From 6b487bcf5d1b8a2fb75747038a6daeba3c79fb99 Mon Sep 17 00:00:00 2001 From: Mattias Arthursson Date: Wed, 28 May 2008 08:28:07 +0000 Subject: [PATCH] Added maven build structure for Spring LDAP project. Left to be done: Integration tests using ddsteps are not included in new structure Javadocs and copyright statements in test-support project Packaging configuration - building the distributables from root maven pom. --- mvn-build/core/build-instructions.txt | 41 + mvn-build/core/build.xml | 91 + mvn-build/core/changelog.txt | 365 ++++ mvn-build/core/docs/quickstart/quickstart.pdf | Bin 0 -> 54787 bytes .../docs/reference/images/admons/blank.png | Bin 0 -> 374 bytes .../docs/reference/images/admons/caution.gif | Bin 0 -> 743 bytes .../docs/reference/images/admons/caution.png | Bin 0 -> 1250 bytes .../docs/reference/images/admons/caution.tif | Bin 0 -> 1978 bytes .../docs/reference/images/admons/draft.png | Bin 0 -> 17454 bytes .../docs/reference/images/admons/home.gif | Bin 0 -> 321 bytes .../docs/reference/images/admons/home.png | Bin 0 -> 1156 bytes .../reference/images/admons/important.gif | Bin 0 -> 1003 bytes .../reference/images/admons/important.png | Bin 0 -> 722 bytes .../reference/images/admons/important.tif | Bin 0 -> 2020 bytes .../docs/reference/images/admons/next.gif | Bin 0 -> 1083 bytes .../docs/reference/images/admons/next.png | Bin 0 -> 1150 bytes .../docs/reference/images/admons/note.gif | Bin 0 -> 580 bytes .../docs/reference/images/admons/note.png | Bin 0 -> 490 bytes .../docs/reference/images/admons/note.tif | Bin 0 -> 460 bytes .../docs/reference/images/admons/prev.gif | Bin 0 -> 1118 bytes .../docs/reference/images/admons/prev.png | Bin 0 -> 1132 bytes .../core/docs/reference/images/admons/tip.gif | Bin 0 -> 598 bytes .../core/docs/reference/images/admons/tip.png | Bin 0 -> 449 bytes .../core/docs/reference/images/admons/tip.tif | Bin 0 -> 420 bytes .../reference/images/admons/toc-blank.png | Bin 0 -> 318 bytes .../reference/images/admons/toc-minus.png | Bin 0 -> 259 bytes .../docs/reference/images/admons/toc-plus.png | Bin 0 -> 264 bytes .../core/docs/reference/images/admons/up.gif | Bin 0 -> 1089 bytes .../core/docs/reference/images/admons/up.png | Bin 0 -> 1111 bytes .../docs/reference/images/admons/warning.gif | Bin 0 -> 743 bytes .../docs/reference/images/admons/warning.png | Bin 0 -> 1241 bytes .../docs/reference/images/admons/warning.tif | Bin 0 -> 1990 bytes .../core/docs/reference/images/callouts/1.gif | Bin 0 -> 968 bytes .../core/docs/reference/images/callouts/1.png | Bin 0 -> 329 bytes .../docs/reference/images/callouts/10.gif | Bin 0 -> 968 bytes .../docs/reference/images/callouts/10.png | Bin 0 -> 361 bytes .../docs/reference/images/callouts/11.gif | Bin 0 -> 244 bytes .../docs/reference/images/callouts/11.png | Bin 0 -> 565 bytes .../docs/reference/images/callouts/12.gif | Bin 0 -> 244 bytes .../docs/reference/images/callouts/12.png | Bin 0 -> 617 bytes .../docs/reference/images/callouts/13.gif | Bin 0 -> 244 bytes .../docs/reference/images/callouts/13.png | Bin 0 -> 623 bytes .../docs/reference/images/callouts/14.gif | Bin 0 -> 244 bytes .../docs/reference/images/callouts/14.png | Bin 0 -> 411 bytes .../docs/reference/images/callouts/15.gif | Bin 0 -> 244 bytes .../docs/reference/images/callouts/15.png | Bin 0 -> 640 bytes .../core/docs/reference/images/callouts/2.gif | Bin 0 -> 968 bytes .../core/docs/reference/images/callouts/2.png | Bin 0 -> 353 bytes .../core/docs/reference/images/callouts/3.gif | Bin 0 -> 968 bytes .../core/docs/reference/images/callouts/3.png | Bin 0 -> 350 bytes .../core/docs/reference/images/callouts/4.gif | Bin 0 -> 968 bytes .../core/docs/reference/images/callouts/4.png | Bin 0 -> 345 bytes .../core/docs/reference/images/callouts/5.gif | Bin 0 -> 968 bytes .../core/docs/reference/images/callouts/5.png | Bin 0 -> 348 bytes .../core/docs/reference/images/callouts/6.gif | Bin 0 -> 968 bytes .../core/docs/reference/images/callouts/6.png | Bin 0 -> 355 bytes .../core/docs/reference/images/callouts/7.gif | Bin 0 -> 968 bytes .../core/docs/reference/images/callouts/7.png | Bin 0 -> 344 bytes .../core/docs/reference/images/callouts/8.gif | Bin 0 -> 968 bytes .../core/docs/reference/images/callouts/8.png | Bin 0 -> 357 bytes .../core/docs/reference/images/callouts/9.gif | Bin 0 -> 968 bytes .../core/docs/reference/images/callouts/9.png | Bin 0 -> 357 bytes mvn-build/core/docs/reference/readme.txt | 20 + mvn-build/core/docs/reference/src/basic.xml | 385 ++++ .../core/docs/reference/src/configuration.xml | 259 +++ .../docs/reference/src/contextprocessor.xml | 200 ++ .../docs/reference/src/dirobjectfactory.xml | 341 ++++ .../core/docs/reference/src/executors.xml | 148 ++ .../reference/src/images/NamingException.png | Bin 0 -> 64786 bytes .../docs/reference/src/images/banner4.jpg | Bin 0 -> 83374 bytes .../docs/reference/src/images/bannerR.gif | Bin 0 -> 7949 bytes .../core/docs/reference/src/images/logo.png | Bin 0 -> 23834 bytes .../src/images/package-dependencies.png | Bin 0 -> 14893 bytes .../src/images/package-structure.png | Bin 0 -> 16302 bytes mvn-build/core/docs/reference/src/index.xml | 64 + .../core/docs/reference/src/overview.xml | 417 ++++ mvn-build/core/docs/reference/src/pooling.xml | 550 +++++ mvn-build/core/docs/reference/src/preface.xml | 32 + mvn-build/core/docs/reference/src/simple.xml | 35 + .../core/docs/reference/src/transactions.xml | 216 ++ .../core/docs/reference/styles/fopdf.xsl | 475 +++++ mvn-build/core/docs/reference/styles/html.css | 270 +++ mvn-build/core/docs/reference/styles/html.xsl | 100 + .../core/docs/reference/styles/html_chunk.xsl | 103 + .../docs/reference/styles/tld.to.docbook.xsl | 197 ++ .../docs/reference/styles/xsd.to.docbook.xsl | 123 ++ mvn-build/core/docs/upgrade/src/readme.txt | 19 + mvn-build/core/docs/upgrade/src/upgrade.xml | 460 +++++ mvn-build/core/itest-openldap-targets.xml | 234 +++ mvn-build/core/ivy.xml | 140 ++ mvn-build/core/license.txt | 201 ++ mvn-build/core/notice.txt | 16 + mvn-build/core/pom.xml | 116 ++ mvn-build/core/project.properties | 17 + mvn-build/core/project.xml | 240 +++ mvn-build/core/readme.txt | 87 + .../java/conf/ldap-openldap.properties | 4 + .../src/itest-openldap/java/conf/ldap.conf | 28 + .../conf/ldapTemplateTestContext-openldap.xml | 24 + .../ldap/LdapTemplateLookupOpenLdapITest.java | 220 ++ .../control/LdapTemplatePagedSearchITest.java | 157 ++ .../LdapTemplateSortedSearchITest.java | 143 ++ .../ldap/AttributeInUseException.java | 32 + .../ldap/AttributeModificationException.java | 32 + .../ldap/AuthenticationException.java | 31 + .../AuthenticationNotSupportedException.java | 33 + .../ldap/BadLdapGrammarException.java | 36 + .../ldap/CannotProceedException.java | 31 + .../ldap/CommunicationException.java | 31 + .../ldap/ConfigurationException.java | 31 + .../ldap/ContextNotEmptyException.java | 31 + .../ldap/InsufficientResourcesException.java | 32 + .../ldap/InterruptedNamingException.java | 32 + .../InvalidAttributeIdentifierException.java | 32 + .../ldap/InvalidAttributeValueException.java | 32 + .../ldap/InvalidAttributesException.java | 32 + .../ldap/InvalidNameException.java | 31 + .../ldap/InvalidSearchControlsException.java | 32 + .../ldap/InvalidSearchFilterException.java | 32 + .../ldap/LdapReferralException.java | 35 + .../ldap/LimitExceededException.java | 31 + .../springframework/ldap/LinkException.java | 31 + .../ldap/LinkLoopException.java | 31 + .../ldap/MalformedLinkException.java | 31 + .../ldap/NameAlreadyBoundException.java | 32 + .../ldap/NameNotFoundException.java | 35 + .../springframework/ldap/NamingException.java | 186 ++ .../ldap/NamingSecurityException.java | 31 + .../ldap/NoInitialContextException.java | 32 + .../ldap/NoPermissionException.java | 31 + .../ldap/NoSuchAttributeException.java | 32 + .../ldap/NotContextException.java | 31 + .../ldap/OperationNotSupportedException.java | 32 + .../ldap/PartialResultException.java | 31 + .../ldap/ReferralException.java | 31 + .../ldap/SchemaViolationException.java | 32 + .../ldap/ServiceUnavailableException.java | 32 + .../ldap/SizeLimitExceededException.java | 32 + .../ldap/TimeLimitExceededException.java | 32 + .../ldap/UncategorizedLdapException.java | 38 + .../AcegiAuthenticationSource.java | 87 + ...ltValuesAuthenticationSourceDecorator.java | 157 ++ .../ldap/authentication/package.html | 7 + ...ractRequestControlDirContextProcessor.java | 93 + .../control/CreateControlFailedException.java | 51 + .../ldap/control/PagedResult.java | 92 + .../ldap/control/PagedResultsCookie.java | 78 + .../control/PagedResultsRequestControl.java | 238 +++ .../SortControlDirContextProcessor.java | 247 +++ .../springframework/ldap/control/package.html | 7 + .../core/AttributeModificationsAware.java | 37 + .../ldap/core/AttributesMapper.java | 55 + .../core/AttributesMapperCallbackHandler.java | 66 + .../ldap/core/AuthenticationSource.java | 40 + ...ollectingNameClassPairCallbackHandler.java | 63 + .../ldap/core/ContextAssembler.java | 36 + .../ldap/core/ContextExecutor.java | 50 + .../ldap/core/ContextMapper.java | 65 + .../core/ContextMapperCallbackHandler.java | 65 + .../ldap/core/ContextSource.java | 51 + .../ldap/core/DefaultDnParserFactory.java | 40 + .../ldap/core/DefaultNameClassPairMapper.java | 46 + .../ldap/core/DirContextAdapter.java | 1254 ++++++++++++ .../ldap/core/DirContextOperations.java | 202 ++ .../ldap/core/DirContextProcessor.java | 51 + .../ldap/core/DirContextProxy.java | 34 + .../ldap/core/DistinguishedName.java | 738 +++++++ .../ldap/core/DistinguishedNameEditor.java | 57 + .../springframework/ldap/core/DnParser.java | 39 + .../ldap/core/LdapEncoder.java | 230 +++ .../ldap/core/LdapOperations.java | 1575 +++++++++++++++ .../springframework/ldap/core/LdapRdn.java | 258 +++ .../ldap/core/LdapRdnComponent.java | 195 ++ .../ldap/core/LdapTemplate.java | 1339 +++++++++++++ .../core/NameClassPairCallbackHandler.java | 41 + .../ldap/core/NameClassPairMapper.java | 44 + .../ldap/core/ObjectRetrievalException.java | 53 + .../ldap/core/SearchExecutor.java | 52 + .../springframework/ldap/core/package.html | 8 + .../core/support/AbstractContextMapper.java | 58 + .../core/support/AbstractContextSource.java | 532 +++++ .../support/AggregateDirContextProcessor.java | 88 + .../ldap/core/support/BaseLdapPathAware.java | 49 + .../BaseLdapPathBeanPostProcessor.java | 126 ++ .../support/BaseLdapPathContextSource.java | 28 + .../ldap/core/support/BaseLdapPathSource.java | 46 + .../CountNameClassPairCallbackHandler.java | 51 + .../core/support/DefaultDirObjectFactory.java | 97 + .../ldap/core/support/DirContextSource.java | 48 + .../ldap/core/support/LdapContextSource.java | 45 + .../ldap/core/support/package.html | 7 + .../ldap/filter/AbstractFilter.java | 51 + .../ldap/filter/AndFilter.java | 57 + .../ldap/filter/BinaryLogicalFilter.java | 97 + .../ldap/filter/CompareFilter.java | 123 ++ .../ldap/filter/EqualsFilter.java | 59 + .../springframework/ldap/filter/Filter.java | 59 + .../filter/GreaterThanOrEqualsFilter.java | 54 + .../ldap/filter/LessThanOrEqualsFilter.java | 54 + .../ldap/filter/LikeFilter.java | 75 + .../ldap/filter/NotFilter.java | 84 + .../springframework/ldap/filter/OrFilter.java | 56 + .../filter/WhitespaceWildcardsFilter.java | 79 + .../springframework/ldap/filter/package.html | 20 + .../org/springframework/ldap/package.html | 7 + .../ldap/pool/DelegatingContext.java | 381 ++++ .../ldap/pool/DelegatingDirContext.java | 361 ++++ .../ldap/pool/DelegatingLdapContext.java | 199 ++ .../ldap/pool/DirContextType.java | 53 + .../DirContextPoolableObjectFactory.java | 204 ++ .../pool/factory/PoolingContextSource.java | 406 ++++ .../ldap/pool/factory/package.html | 7 + .../springframework/ldap/pool/package.html | 7 + .../DefaultDirContextValidator.java | 173 ++ .../pool/validation/DirContextValidator.java | 39 + .../ldap/pool/validation/package.html | 7 + .../ldap/support/LdapUtils.java | 209 ++ .../ldap/support/ListComparator.java | 65 + .../springframework/ldap/support/package.html | 7 + .../compensating/BindOperationExecutor.java | 127 ++ .../compensating/BindOperationRecorder.java | 79 + ...mpensatingTransactionOperationFactory.java | 194 ++ .../compensating/LdapTransactionUtils.java | 121 ++ .../ModifyAttributesOperationExecutor.java | 116 ++ .../ModifyAttributesOperationRecorder.java | 165 ++ .../compensating/NullOperationExecutor.java | 47 + .../compensating/NullOperationRecorder.java | 43 + .../compensating/RebindOperationExecutor.java | 135 ++ .../compensating/RebindOperationRecorder.java | 93 + .../compensating/RenameOperationExecutor.java | 101 + .../compensating/RenameOperationRecorder.java | 72 + .../TempEntryRenamingStrategy.java | 38 + .../compensating/UnbindOperationExecutor.java | 106 + .../compensating/UnbindOperationRecorder.java | 74 + ...SourceAndDataSourceTransactionManager.java | 202 ++ .../ContextSourceTransactionManager.java | 190 ++ ...ntextSourceTransactionManagerDelegate.java | 121 ++ .../manager/DirContextHolder.java | 73 + .../TransactionAwareContextSourceProxy.java | 101 + ...ctionAwareDirContextInvocationHandler.java | 123 ++ .../compensating/manager/package.html | 7 + .../transaction/compensating/package.html | 7 + .../DefaultTempEntryRenamingStrategy.java | 94 + ...erentSubtreeTempEntryRenamingStrategy.java | 89 + .../compensating/support/package.html | 7 + ...pensatingTransactionOperationExecutor.java | 62 + ...mpensatingTransactionOperationFactory.java | 44 + ...mpensatingTransactionOperationManager.java | 55 + ...pensatingTransactionOperationRecorder.java | 41 + .../transaction/compensating/package.html | 8 + ...ompensatingTransactionManagerDelegate.java | 130 ++ .../CompensatingTransactionHolderSupport.java | 80 + .../CompensatingTransactionObject.java | 62 + .../support/CompensatingTransactionUtils.java | 76 + ...mpensatingTransactionOperationManager.java | 128 ++ .../compensating/support/package.html | 9 + mvn-build/core/src/main/java/overview.html | 3 + .../core/src/main/javacc/DnParserImpl.jj | 154 ++ .../ldap/NamingExceptionTest.java | 76 + .../AcegiAuthenticationSourceTest.java | 144 ++ ...luesAuthenticationSourceDecoratorTest.java | 134 ++ .../ldap/control/PagedResultTest.java | 49 + .../ldap/control/PagedResultsCookieTest.java | 43 + .../PagedResultsRequestControlTest.java | 207 ++ ...RequestControlDirContextProcessorTest.java | 159 ++ .../SortControlDirContextProcessorTest.java | 193 ++ ...ctingNameClassPairCallbackHandlerTest.java | 63 + .../ContextMapperCallbackHandlerTest.java | 88 + .../ldap/core/DirContextAdapterBugTest.java | 90 + .../ldap/core/DirContextAdapterTest.java | 1005 ++++++++++ .../core/DistinguishedNameEditorTest.java | 71 + .../ldap/core/DistinguishedNameTest.java | 501 +++++ .../ldap/core/LdapEncoderTest.java | 71 + .../ldap/core/LdapRdnComponentTest.java | 57 + .../ldap/core/LdapRdnTest.java | 240 +++ .../ldap/core/LdapTemplateListTest.java | 419 ++++ .../ldap/core/LdapTemplateLookupTest.java | 426 ++++ .../ldap/core/LdapTemplateRenameTest.java | 171 ++ .../ldap/core/LdapTemplateTest.java | 1785 +++++++++++++++++ .../ldap/core/RequestControlMatcher.java | 72 + .../AggregateDirContextProcessorTest.java | 100 + .../BaseLdapPathBeanPostProcessorTest.java | 159 ++ ...ameClassPairResultCallbackHandlerTest.java | 51 + .../support/DefaultDirObjectFactoryTest.java | 156 ++ .../core/support/LdapContextSourceTest.java | 288 +++ .../ldap/filter/AbstractFilterTest.java | 67 + .../ldap/filter/AndFilterTest.java | 79 + .../ldap/filter/EqualsFilterTest.java | 71 + .../filter/GreaterThanOrEqualsFilterTest.java | 61 + .../filter/LessThanOrEqualsFilterTest.java | 60 + .../ldap/filter/LikeFilterTest.java | 56 + .../ldap/filter/NotFilterTest.java | 50 + .../ldap/filter/OrFilterTest.java | 64 + .../filter/WhitespaceWildcardsFilterTest.java | 75 + .../ldap/pool/AbstractPoolTestCase.java | 123 ++ .../ldap/pool/DelegatingContextTest.java | 446 ++++ .../ldap/pool/DelegatingDirContextTest.java | 419 ++++ .../ldap/pool/DelegatingLdapContextTest.java | 287 +++ .../DirContextPoolableObjectFactoryTest.java | 243 +++ .../factory/PoolingContextSourceTest.java | 273 +++ .../DefaultDirContextValidatorTest.java | 187 ++ .../BindOperationExecutorTest.java | 95 + .../BindOperationRecorderTest.java | 98 + ...satingTransactionOperationFactoryTest.java | 134 ++ .../LdapTransactionUtilsTest.java | 90 + ...ModifyAttributesOperationExecutorTest.java | 109 + ...ModifyAttributesOperationRecorderTest.java | 271 +++ .../RebindOperationExecutorTest.java | 110 + .../RebindOperationRecorderTest.java | 99 + .../RenameOperationExecutorTest.java | 90 + .../RenameOperationRecorderTest.java | 67 + .../UnbindOperationExecutorTest.java | 91 + .../UnbindOperationRecorderTest.java | 92 + .../ContextSourceTransactionManagerTest.java | 201 ++ ...ransactionAwareContextSourceProxyTest.java | 127 ++ ...nAwareDirContextInvocationHandlerTest.java | 113 ++ .../DefaultTempEntryRenamingStrategyTest.java | 49 + ...tSubtreeTempEntryRenamingStrategyTest.java | 43 + .../ldap/util/ListComparatorTest.java | 92 + .../CompensatingTransactionUtilsTest.java | 123 ++ ...satingTransactionOperationManagerTest.java | 168 ++ .../core/src/test/resources/log4j.properties | 4 + mvn-build/core/xdocs/navigation.xml | 43 + .../core-integration-tests/pom.xml | 101 + .../java/org/springframework/ldap/Person.java | 64 + .../ldap/PersonAttributesMapper.java | 47 + .../ldap/PersonContextMapper.java | 39 + .../compensating/manager/DummyDao.java | 44 + .../compensating/manager/DummyException.java | 22 + .../manager/DummyServiceImpl.java | 24 + .../manager/LdapAndJdbcDummyDaoImpl.java | 178 ++ .../manager/LdapDummyDaoImpl.java | 163 ++ .../src/test/java/log4j.properties | 9 + .../AbstractLdapTemplateIntegrationTest.java | 27 + .../LdapTemplateAttributesMapperITest.java | 83 + .../ldap/LdapTemplateBindUnbindITest.java | 119 ++ .../ldap/LdapTemplateContextExecutorTest.java | 61 + .../ldap/LdapTemplateContextMapperITest.java | 79 + .../ldap/LdapTemplateListITest.java | 148 ++ .../ldap/LdapTemplateLookupITest.java | 245 +++ .../ldap/LdapTemplateModifyITest.java | 253 +++ .../ldap/LdapTemplateNoBaseSuffixITest.java | 98 + ...LdapTemplateObjectBindIntegrationTest.java | 145 ++ .../LdapTemplateRecursiveDeleteITest.java | 127 ++ .../ldap/LdapTemplateRenameITest.java | 100 + .../ldap/LdapTemplateSearchCountITest.xls | Bin 0 -> 11776 bytes .../ldap/LdapTemplateSearchResultITest.java | 181 ++ .../AcegiAuthenticationSourceITest.java | 81 + .../ldap/control/SupportedControlsITest.java | 70 + .../core/DistinguishedNameEditorITest.java | 43 + .../ldap/core/DnParsePerformanceITest.java | 67 + .../core/DummyDistinguishedNameConsumer.java | 34 + .../BaseLdapPathBeanPostprocessorITest.java | 97 + .../core/support/DummyBaseLdapPathAware.java | 37 + .../core/support/LdapContextSourcelITest.java | 89 + .../LdapContextSourcelMultiServerITest.java | 46 + ...urceTransactionManagerIntegrationTest.java | 371 ++++ ...urceTransactionManagerIntegrationTest.java | 281 +++ .../src/test/java/setup_data.ldif | 110 + .../src/test/java/teardown_data.ldif | 5 + .../test/resources/conf/apacheDsContext.xml | 89 + ...orMultiContextSourceOneSpecTestContext.xml | 40 + ...ProcessorMultiContextSourceTestContext.xml | 38 + ...ostProcessorNoContextSourceTestContext.xml | 13 + ...stProcessorPropertyOverrideTestContext.xml | 30 + .../baseLdapPathPostProcessorTestContext.xml | 28 + ...athPostProcessorTransactionTestContext.xml | 33 + .../test/resources/conf/commonTestContext.xml | 16 + .../distinguishedNameEditorTestContext.xml | 11 + .../src/test/resources/conf/ldap.properties | 4 + .../ldapAndJdbcTransactionTestContext.xml | 66 + .../conf/ldapContextSourceTestContext.xml | 11 + .../conf/ldapTemplateAcegiTestContext.xml | 66 + .../ldapTemplateNoBaseSuffixTestContext.xml | 27 + .../ldapTemplateObjectBindTestContext.xml | 24 + .../conf/ldapTemplateTestContext.xml | 26 + .../ldapTemplateTransactionTestContext.xml | 52 + .../conf/rootContextSourceTestContext.xml | 25 + .../src/test/resources/log4j.properties | 6 + .../src/test/resources/setup_data.ldif | 110 + mvn-build/integration-tests/pom.xml | 18 + .../tiger-integration-tests/pom.xml | 100 + .../core/simple/SimpleLdapTemplateITest.java | 76 + .../src/test/resources/conf/ldap.properties | 4 + .../conf/ldapTemplateTestContext.xml | 29 + .../src/test/resources/log4j.properties | 9 + .../src/test/resources/setup_data.ldif | 110 + mvn-build/lib/ldapbp.jar | Bin 0 -> 49437 bytes mvn-build/pom.xml | 104 + mvn-build/test-support/pom.xml | 85 + .../test/AttributeCheckAttributesMapper.java | 68 + .../test/AttributeCheckContextMapper.java | 66 + .../ldap/test/LdapTestUtils.java | 205 ++ .../test/TestContextSourceFactoryBean.java | 117 ++ mvn-build/tiger/pom.xml | 36 + .../AbstractParameterizedContextMapper.java | 53 + .../simple/ParameterizedContextMapper.java | 26 + .../core/simple/SimpleLdapOperations.java | 142 ++ .../ldap/core/simple/SimpleLdapTemplate.java | 137 ++ .../ldap/core/simple/package.html | 7 + mvn-build/tiger/src/main/java/overview.html | 3 + 401 files changed, 40782 insertions(+) create mode 100644 mvn-build/core/build-instructions.txt create mode 100644 mvn-build/core/build.xml create mode 100644 mvn-build/core/changelog.txt create mode 100644 mvn-build/core/docs/quickstart/quickstart.pdf create mode 100644 mvn-build/core/docs/reference/images/admons/blank.png create mode 100644 mvn-build/core/docs/reference/images/admons/caution.gif create mode 100644 mvn-build/core/docs/reference/images/admons/caution.png create mode 100644 mvn-build/core/docs/reference/images/admons/caution.tif create mode 100644 mvn-build/core/docs/reference/images/admons/draft.png create mode 100644 mvn-build/core/docs/reference/images/admons/home.gif create mode 100644 mvn-build/core/docs/reference/images/admons/home.png create mode 100644 mvn-build/core/docs/reference/images/admons/important.gif create mode 100644 mvn-build/core/docs/reference/images/admons/important.png create mode 100644 mvn-build/core/docs/reference/images/admons/important.tif create mode 100644 mvn-build/core/docs/reference/images/admons/next.gif create mode 100644 mvn-build/core/docs/reference/images/admons/next.png create mode 100644 mvn-build/core/docs/reference/images/admons/note.gif create mode 100644 mvn-build/core/docs/reference/images/admons/note.png create mode 100644 mvn-build/core/docs/reference/images/admons/note.tif create mode 100644 mvn-build/core/docs/reference/images/admons/prev.gif create mode 100644 mvn-build/core/docs/reference/images/admons/prev.png create mode 100644 mvn-build/core/docs/reference/images/admons/tip.gif create mode 100644 mvn-build/core/docs/reference/images/admons/tip.png create mode 100644 mvn-build/core/docs/reference/images/admons/tip.tif create mode 100644 mvn-build/core/docs/reference/images/admons/toc-blank.png create mode 100644 mvn-build/core/docs/reference/images/admons/toc-minus.png create mode 100644 mvn-build/core/docs/reference/images/admons/toc-plus.png create mode 100644 mvn-build/core/docs/reference/images/admons/up.gif create mode 100644 mvn-build/core/docs/reference/images/admons/up.png create mode 100644 mvn-build/core/docs/reference/images/admons/warning.gif create mode 100644 mvn-build/core/docs/reference/images/admons/warning.png create mode 100644 mvn-build/core/docs/reference/images/admons/warning.tif create mode 100644 mvn-build/core/docs/reference/images/callouts/1.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/1.png create mode 100644 mvn-build/core/docs/reference/images/callouts/10.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/10.png create mode 100644 mvn-build/core/docs/reference/images/callouts/11.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/11.png create mode 100644 mvn-build/core/docs/reference/images/callouts/12.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/12.png create mode 100644 mvn-build/core/docs/reference/images/callouts/13.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/13.png create mode 100644 mvn-build/core/docs/reference/images/callouts/14.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/14.png create mode 100644 mvn-build/core/docs/reference/images/callouts/15.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/15.png create mode 100644 mvn-build/core/docs/reference/images/callouts/2.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/2.png create mode 100644 mvn-build/core/docs/reference/images/callouts/3.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/3.png create mode 100644 mvn-build/core/docs/reference/images/callouts/4.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/4.png create mode 100644 mvn-build/core/docs/reference/images/callouts/5.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/5.png create mode 100644 mvn-build/core/docs/reference/images/callouts/6.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/6.png create mode 100644 mvn-build/core/docs/reference/images/callouts/7.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/7.png create mode 100644 mvn-build/core/docs/reference/images/callouts/8.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/8.png create mode 100644 mvn-build/core/docs/reference/images/callouts/9.gif create mode 100644 mvn-build/core/docs/reference/images/callouts/9.png create mode 100644 mvn-build/core/docs/reference/readme.txt create mode 100644 mvn-build/core/docs/reference/src/basic.xml create mode 100644 mvn-build/core/docs/reference/src/configuration.xml create mode 100644 mvn-build/core/docs/reference/src/contextprocessor.xml create mode 100644 mvn-build/core/docs/reference/src/dirobjectfactory.xml create mode 100644 mvn-build/core/docs/reference/src/executors.xml create mode 100644 mvn-build/core/docs/reference/src/images/NamingException.png create mode 100644 mvn-build/core/docs/reference/src/images/banner4.jpg create mode 100644 mvn-build/core/docs/reference/src/images/bannerR.gif create mode 100644 mvn-build/core/docs/reference/src/images/logo.png create mode 100644 mvn-build/core/docs/reference/src/images/package-dependencies.png create mode 100644 mvn-build/core/docs/reference/src/images/package-structure.png create mode 100644 mvn-build/core/docs/reference/src/index.xml create mode 100644 mvn-build/core/docs/reference/src/overview.xml create mode 100644 mvn-build/core/docs/reference/src/pooling.xml create mode 100644 mvn-build/core/docs/reference/src/preface.xml create mode 100644 mvn-build/core/docs/reference/src/simple.xml create mode 100644 mvn-build/core/docs/reference/src/transactions.xml create mode 100644 mvn-build/core/docs/reference/styles/fopdf.xsl create mode 100644 mvn-build/core/docs/reference/styles/html.css create mode 100644 mvn-build/core/docs/reference/styles/html.xsl create mode 100644 mvn-build/core/docs/reference/styles/html_chunk.xsl create mode 100644 mvn-build/core/docs/reference/styles/tld.to.docbook.xsl create mode 100644 mvn-build/core/docs/reference/styles/xsd.to.docbook.xsl create mode 100644 mvn-build/core/docs/upgrade/src/readme.txt create mode 100644 mvn-build/core/docs/upgrade/src/upgrade.xml create mode 100644 mvn-build/core/itest-openldap-targets.xml create mode 100644 mvn-build/core/ivy.xml create mode 100644 mvn-build/core/license.txt create mode 100644 mvn-build/core/notice.txt create mode 100644 mvn-build/core/pom.xml create mode 100644 mvn-build/core/project.properties create mode 100644 mvn-build/core/project.xml create mode 100644 mvn-build/core/readme.txt create mode 100644 mvn-build/core/src/itest-openldap/java/conf/ldap-openldap.properties create mode 100644 mvn-build/core/src/itest-openldap/java/conf/ldap.conf create mode 100644 mvn-build/core/src/itest-openldap/java/conf/ldapTemplateTestContext-openldap.xml create mode 100644 mvn-build/core/src/itest-openldap/java/org/springframework/ldap/LdapTemplateLookupOpenLdapITest.java create mode 100644 mvn-build/core/src/itest-openldap/java/org/springframework/ldap/control/LdapTemplatePagedSearchITest.java create mode 100644 mvn-build/core/src/itest-openldap/java/org/springframework/ldap/control/LdapTemplateSortedSearchITest.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/AttributeInUseException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/AttributeModificationException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/AuthenticationException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/AuthenticationNotSupportedException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/BadLdapGrammarException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/CannotProceedException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/CommunicationException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/ConfigurationException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/ContextNotEmptyException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/InsufficientResourcesException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/InterruptedNamingException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributeIdentifierException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributeValueException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributesException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/InvalidNameException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/InvalidSearchControlsException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/InvalidSearchFilterException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/LdapReferralException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/LimitExceededException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/LinkException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/LinkLoopException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/MalformedLinkException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/NameAlreadyBoundException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/NameNotFoundException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/NamingException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/NamingSecurityException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/NoInitialContextException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/NoPermissionException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/NoSuchAttributeException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/NotContextException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/OperationNotSupportedException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/PartialResultException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/ReferralException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/SchemaViolationException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/ServiceUnavailableException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/SizeLimitExceededException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/TimeLimitExceededException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/UncategorizedLdapException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/authentication/AcegiAuthenticationSource.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecorator.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/authentication/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/control/AbstractRequestControlDirContextProcessor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/control/CreateControlFailedException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResult.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResultsCookie.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResultsRequestControl.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/control/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/AttributeModificationsAware.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/AttributesMapper.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/AttributesMapperCallbackHandler.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/AuthenticationSource.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/CollectingNameClassPairCallbackHandler.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/ContextAssembler.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/ContextExecutor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/ContextMapper.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/ContextMapperCallbackHandler.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/ContextSource.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/DefaultDnParserFactory.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/DefaultNameClassPairMapper.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextAdapter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextOperations.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextProcessor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextProxy.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/DistinguishedName.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/DistinguishedNameEditor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/DnParser.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/LdapEncoder.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/LdapOperations.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/LdapRdn.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/LdapRdnComponent.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/NameClassPairCallbackHandler.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/NameClassPairMapper.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/ObjectRetrievalException.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/SearchExecutor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/AbstractContextMapper.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/AbstractContextSource.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/AggregateDirContextProcessor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathAware.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostProcessor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathContextSource.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathSource.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/CountNameClassPairCallbackHandler.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/DefaultDirObjectFactory.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/DirContextSource.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/LdapContextSource.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/core/support/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/AbstractFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/AndFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/BinaryLogicalFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/CompareFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/EqualsFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/Filter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/GreaterThanOrEqualsFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/LessThanOrEqualsFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/LikeFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/NotFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/OrFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/WhitespaceWildcardsFilter.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/filter/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingContext.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingDirContext.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingLdapContext.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/DirContextType.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/DirContextPoolableObjectFactory.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/PoolingContextSource.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/DefaultDirContextValidator.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/DirContextValidator.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/support/LdapUtils.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/support/ListComparator.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/support/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/BindOperationExecutor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/BindOperationRecorder.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactory.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/LdapTransactionUtils.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorder.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/NullOperationExecutor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/NullOperationRecorder.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RebindOperationExecutor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RebindOperationRecorder.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RenameOperationExecutor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RenameOperationRecorder.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/TempEntryRenamingStrategy.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/UnbindOperationExecutor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/UnbindOperationRecorder.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManager.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManager.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/DirContextHolder.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxy.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandler.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategy.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategy.java create mode 100644 mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationExecutor.java create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationFactory.java create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationManager.java create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationRecorder.java create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/package.html create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionHolderSupport.java create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionObject.java create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionUtils.java create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManager.java create mode 100644 mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/package.html create mode 100644 mvn-build/core/src/main/java/overview.html create mode 100644 mvn-build/core/src/main/javacc/DnParserImpl.jj create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/NamingExceptionTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/authentication/AcegiAuthenticationSourceTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecoratorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultsCookieTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultsRequestControlTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/control/RequestControlDirContextProcessorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/control/SortControlDirContextProcessorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/CollectingNameClassPairCallbackHandlerTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/ContextMapperCallbackHandlerTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/DirContextAdapterBugTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/DirContextAdapterTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/DistinguishedNameEditorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/DistinguishedNameTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/LdapEncoderTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/LdapRdnComponentTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/LdapRdnTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateListTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateLookupTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateRenameTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/RequestControlMatcher.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/support/AggregateDirContextProcessorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostProcessorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/support/CountNameClassPairResultCallbackHandlerTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/support/DefaultDirObjectFactoryTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/core/support/LdapContextSourceTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/filter/AbstractFilterTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/filter/AndFilterTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/filter/EqualsFilterTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/filter/GreaterThanOrEqualsFilterTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/filter/LessThanOrEqualsFilterTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/filter/LikeFilterTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/filter/NotFilterTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/filter/OrFilterTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/filter/WhitespaceWildcardsFilterTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/pool/AbstractPoolTestCase.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingContextTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingDirContextTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingLdapContextTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/pool/factory/DirContextPoolableObjectFactoryTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/pool/factory/PoolingContextSourceTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/pool/validation/DefaultDirContextValidatorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/BindOperationExecutorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/BindOperationRecorderTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactoryTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/LdapTransactionUtilsTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorderTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RebindOperationExecutorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RebindOperationRecorderTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RenameOperationExecutorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RenameOperationRecorderTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/UnbindOperationExecutorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/UnbindOperationRecorderTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxyTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandlerTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategyTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategyTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/ldap/util/ListComparatorTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/transaction/compensating/support/CompensatingTransactionUtilsTest.java create mode 100644 mvn-build/core/src/test/java/org/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManagerTest.java create mode 100644 mvn-build/core/src/test/resources/log4j.properties create mode 100644 mvn-build/core/xdocs/navigation.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/pom.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/Person.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/PersonAttributesMapper.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/PersonContextMapper.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyDao.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyException.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyServiceImpl.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/LdapAndJdbcDummyDaoImpl.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/LdapDummyDaoImpl.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/log4j.properties create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/AbstractLdapTemplateIntegrationTest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAttributesMapperITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateBindUnbindITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateContextExecutorTest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateContextMapperITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateListITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateLookupITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateModifyITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateNoBaseSuffixITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateObjectBindIntegrationTest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateRecursiveDeleteITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateRenameITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateSearchCountITest.xls create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateSearchResultITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/authentication/AcegiAuthenticationSourceITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/control/SupportedControlsITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DistinguishedNameEditorITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DnParsePerformanceITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DummyDistinguishedNameConsumer.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostprocessorITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/DummyBaseLdapPathAware.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/LdapContextSourcelITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/LdapContextSourcelMultiServerITest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManagerIntegrationTest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerIntegrationTest.java create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/setup_data.ldif create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/java/teardown_data.ldif create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/apacheDsContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorMultiContextSourceOneSpecTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorMultiContextSourceTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorNoContextSourceTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorPropertyOverrideTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorTransactionTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/commonTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/distinguishedNameEditorTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldap.properties create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapAndJdbcTransactionTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapContextSourceTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateAcegiTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateNoBaseSuffixTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateObjectBindTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateTransactionTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/rootContextSourceTestContext.xml create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/log4j.properties create mode 100644 mvn-build/integration-tests/core-integration-tests/src/test/resources/setup_data.ldif create mode 100644 mvn-build/integration-tests/pom.xml create mode 100644 mvn-build/integration-tests/tiger-integration-tests/pom.xml create mode 100644 mvn-build/integration-tests/tiger-integration-tests/src/test/java/org/springframework/ldap/core/simple/SimpleLdapTemplateITest.java create mode 100644 mvn-build/integration-tests/tiger-integration-tests/src/test/resources/conf/ldap.properties create mode 100644 mvn-build/integration-tests/tiger-integration-tests/src/test/resources/conf/ldapTemplateTestContext.xml create mode 100644 mvn-build/integration-tests/tiger-integration-tests/src/test/resources/log4j.properties create mode 100644 mvn-build/integration-tests/tiger-integration-tests/src/test/resources/setup_data.ldif create mode 100644 mvn-build/lib/ldapbp.jar create mode 100644 mvn-build/pom.xml create mode 100644 mvn-build/test-support/pom.xml create mode 100644 mvn-build/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckAttributesMapper.java create mode 100644 mvn-build/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckContextMapper.java create mode 100644 mvn-build/test-support/src/main/java/org/springframework/ldap/test/LdapTestUtils.java create mode 100644 mvn-build/test-support/src/main/java/org/springframework/ldap/test/TestContextSourceFactoryBean.java create mode 100644 mvn-build/tiger/pom.xml create mode 100644 mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/AbstractParameterizedContextMapper.java create mode 100644 mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/ParameterizedContextMapper.java create mode 100644 mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/SimpleLdapOperations.java create mode 100644 mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/SimpleLdapTemplate.java create mode 100644 mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/package.html create mode 100644 mvn-build/tiger/src/main/java/overview.html diff --git a/mvn-build/core/build-instructions.txt b/mvn-build/core/build-instructions.txt new file mode 100644 index 00000000..fddffa69 --- /dev/null +++ b/mvn-build/core/build-instructions.txt @@ -0,0 +1,41 @@ +In order to build the spring-ldap-person sample, you need to build spring-ldap first. + +% cd spring-ldap +% ant dist + +You will most likely get this error, if you haven't installed [url=https://javacc.dev.java.net/]JavaCC[/url] and pointed out to Ant where it is: + +[code] +BUILD FAILED +... JavaCC home must be a valid directory. +[/code] + +You install JavaCC say in /javacc-4.0 and put this in a spring-ldap/build.properties: + +[code] +javacc.home.dir=/javacc-4.0 +[/code] + +Try building spring-ldap again: + +% ant dist + +If that succeeds, you'll get a snapshot build installed in your integration repo: + +% ls ../integration-repo/artifacts/org.springframework/spring-ldap/ +1.2.1-SNAPSHOT-20071206091603 +ivy-1.2.1-SNAPSHOT-20071206091603.xml +ivy-1.2.1-SNAPSHOT-20071206091603.xml.md5 +ivy-1.2.1-SNAPSHOT-20071206091603.xml.sha1 + +Next, you can build spring-ldap-person: + +% cd ../spring-ldap-person +% ant + +If that succeeds, you'll get a war in target/artifacts: + +% ls target/artifacts/war +ldap-person.war + +Deploy that in Tomcat and see how it goes. \ No newline at end of file diff --git a/mvn-build/core/build.xml b/mvn-build/core/build.xml new file mode 100644 index 00000000..6ad8a44e --- /dev/null +++ b/mvn-build/core/build.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Unit test report has been generated. +The HTML report is ${target.testresults.html.dir}/index.html + + + + + + + + + + + diff --git a/mvn-build/core/changelog.txt b/mvn-build/core/changelog.txt new file mode 100644 index 00000000..005a464a --- /dev/null +++ b/mvn-build/core/changelog.txt @@ -0,0 +1,365 @@ +Spring LDAP CHANGELOG +===================== +http://www.springframework.org/ldap + +http://www.ietf.org/rfc/rfc1960.txt +http://www.ietf.org/rfc/rfc2251.txt +http://www.ietf.org/rfc/rfc2252.txt +http://www.ietf.org/rfc/rfc2253.txt +http://www.ietf.org/rfc/rfc2254.txt +http://www.ietf.org/rfc/rfc2255.txt +http://www.ietf.org/rfc/rfc2256.txt +http://www.ietf.org/rfc/rfc2696.txt + +Changes in version 1.2.2 (XXX 2008) +------------------------------------------- +* Upgraded Acegi to 1.0.6. + +* Upgraded commons-lang to 2.3. + +Changes in version 1.2.1 (Dec 2007) +------------------------------------------- +* Added pooling library which features flexible connection validation + and better configuration than the built-in pooling. (LDAP-85) + +* Fixed a problem in AbstractContextSource which led to an unnecessary + reference to the LDAP Booster Pack (ldapbp). (LDAP-88, LDAP-89) + +* Added missing package description and updated copyright. (LDAP-92) + +* Added build instructions. (LDAP-27) + +* Fixed bug in SimpleLdapTemplate where the wrong target method was being + called. (LDAP-93) + +* Made createContext in AbstractContextSource protected rather than + package private. (LDAP-94) + +Changes in version 1.2 (Oct 2007) +------------------------------------------- +A number of API-breaking changes have been made in the 1.2 release compared to version 1.1.2. +Consequently this is NOT a drop-in replacement for Spring LDAP 1.1.2, +though upgrading should not present all that much work. Please see +the supplied upgrade guide for details. + +* Added getContextSource on LdapTemplate. + +* The following getters on AbstractContextSource have been added: + getContextFactory + getDirObjectFactory + getAuthenticationSource + getUrls (was protected) + isPooled + isAnonymousReadOnly + +* Upgraded to Spring 2.0.6 internally. Spring 1.2.x is still supported. + +* Reviewed all javadoc. + +* Revised reference manual. + +* AbstractConnection source now actively removes pooling flag from context environment + if pooling is set to 'false'. (LDAP-83) + +* AcegiAuthenticationSource now supports anonymous authentication. (LDAP-67) + +* Added BaseLdapPathSource, BaseLdapPathAware and BaseLdapNameBeanPostProcessor to be used + if the ContextSource base path is needed by beans. There is now also a DistinguishedNameEditor + available for directly injecting DistinguishedName instances to beans. (LDAP-86) + +* Added immutableDistinguishedName() method in DistinguishedName to get an immutable + copy of the instance. (LDAP-87) + +* Added easier access methods for RDN attributes and values. (LDAP-71) + +* Added lookupContext() and alternate modifyAttributes() in LdapOperations/LdapTemplate + to simplify updates. (LDAP-78) + +* Modified append() method in DistinguishedName. This method now returns the instance, to + enable more fluent building of DistinguishedNames. (LDAP-74) + +* Added SimpleLdapTemplate providing Java 5 generics support. (LDAP-72) + +* Added AbstractContextMapper implementation. (LDAP-76) + +Changes in version 1.2-RC1 (5.12.2007) +------------------------------------------- +A number of API-breaking changes have been made in this release, +mainly package restructuring stuff (LDAP-33). Consequently this is +NOT a drop-in replacement for Spring LDAP 1.1.2, though upgrading +should not present all that much work. Please see the supplied +upgrade guide for details. + +* Restructured packages (LDAP-33): + - Moved the core classes into the package ldap.core. + - Moved subpackages of ldap.support to top level. + - Moved most classes in ldap.support to ldap.core or ldap.core.support. + - Moved classes in ldap.utils to ldap.support and removed ldap.utils. + +* Changed the exception hierarchy to be an unchecked mirror of the JNDI + NamingException hierarchy (LDAP-4). + +* Deprecated the userName property in AbstractContextSource and changed + it to the more correct userDn (LDAP-18). + +* Fixed bug causing PagedResultsRequestControl processing to fail against AD + in the case of a PartialResultsException (LDAP-32). + +* Fixed bug causing NullPointerException in some environments when performing + PagedResultsRequestControl search (LDAP-37). + +* Implemented client-side transaction support for Spring LDAP. See reference + documentation for further information (LDAP-29). + +* DirContextAdapter.setAttribute() now properly updates updatedAttrs in update mode + (resulting in ModificationItems if applicable) (LDAP-15). + +* Exceptions thrown by Spring LDAP are now always Serializable, regardless of whether + the wrapped NamingException is (which is not always the case) (LDAP-14). + +* LdapRdnComponent now implements Serializable (LDAP-45). + +* Rewrote LdapEncoder.nameDecode() to solve problem with national characters and + remove regular expression used in parsing, drastically improving Distinguished Name + parsing performance as a bonus (LDAP-30). + +* The DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY has been deprecated. If the base + property needs to be accessed from a subclass to AbstractContextSource, use the new + protected method getBase (LDAP-55). + +* LdapContextSource.getAnonymousEnv no longer sets a broken ResponseControlFactory in + the environment (LDAP-64). + +* Upgraded ApacheDS to 1.0.0 (LDAP-32). + +* Upgraded to Acegi Security 1.0.3 (LDAP-61). + +* Upgraded to ApacheDS 1.0.0 (LDAP-32). + +* Upgraded to Spring 2.0.4 internally. Spring 1.2.x is still supported (LDAP-35, LDAP-51). + +* Upgraded to Spring WebFlow 1.0.2 in the Person sample (LDAP-60). + +* Fixed a number of documentation errors and typos (LDAP-41, LDAP-40, LDAP-47, LDAP-48). + +* Extracted useful inner classes from LdapTemplate to top-level classes (LDAP-42). + +* Implemented addAttributeValue and removeAttributeValue in DirContextAdapter (LDAP-5). + +* Modified DirContextOperations interface so that getNameInNamespace doesn't throw NamingException. + +* Fixed NullPointerException when getting PagedResultsRequestControl response (LDAP-66). + +* Rewrote logic i DirContextAdapter.getModificationItems(). The JNDI provider's Attribute + comparison logic is now used internally. Also, REMOVE_ATTRIBUTE items are placed + first in the ModificationItem array, in order to prevent ATTRIBUTE_ALREADY_EXIST errors + (LDAP-13, LDAP-46). + +* In internal integration tests, each test now runs on a clean LDAP database (LDAP-43). + +* DirContextProcessor#postProcess() is now called in searches regardless of whether an + exception is thrown (LDAP-26). + +* Moved SortControlDirContextProcessor to spring-ldap from sandbox (LDAP-68). + + +Changes in version 1.1.2 (1.12.2006) +------------------------------------------- +* Fixed problem with DirContextAdapter.getNameInNameSpace. Now it really + returns the full DN. + +* There is now a wrapper class called PagedResult, which can be used to + wrap the resulting list and the PagedResultsCookie after using + PagedResultsRequestControl. + +* The actual cookie in PagedResultsCookie is now immutable. + +* The person sample is now a Spring WebFlow application, where the user + can manage persons and standard LDAP access groups. + +* Improved javadoc for DefaultDirObjectFactory and DistinguishedName. + +* Upgraded Spring to 2.0.1 internally. Spring 1.2.8 is still supported. + +Changes in version 1.1.1 (18.11.2006) +------------------------------------------- +* Added capability to use server-side controls in search by leveraging + DirContextProcessor. + +* Added generic DirContextProcessor that is run before and after a search. + +* Added paged search result functionality in PagedResultsRequestControl, + which is an implementation of DirContextProcessor. + +* Fixed problem with DirContextAdapter, where the wrong change was applied + when a change was followed by a reset to the original values. + +* DirContextAdapter.getNameInNamespace() now returns the full DN, as per the + documentation for javax.naming.Context. + +* The parsing of DistinguishedName has been changed from using regexp to + a JavaCC parser, which means improved performance (5-6 times faster) and + better error messages. + +* DistinguishedName now supports multi-valued RDNs separated by a '+' sign, + like "cn=Rod+sn=Johnson", for example. All according to spec 2253. + +* LdapRdn is now Serializable. + +* Added lookup methods that take an array of return attribute names. + +* Made DistinguishedName.EMPTY_NAME an immutable DistinguishedName instance. + +* Separated integration tests that require OpenLDAP from the other integration + tests that run the in-JVM Apache DS server. + +* Changed the spring-ldap-person sample application to authenticate and + authorize against groups rather than role attributes. + +* Upgraded Spring to 2.0 internally. Spring 1.2.8 is still supported. + +Changes in version 1.1 (28.8.2006) +------------------------------------------- +* Changed base package from net.sf.ldaptemplate to org.springframework.ldap + +* Changed package ...support.acegi to ...support.authentication + +* Replaced SearchResultCallbackHandler with NameClassCallbackHandler + to enable its use in all search operations (search, list, listBindings). + Changed all references and implementing classes accordingly, + including name changes where applicable: + ** CollectingSearchResultCallbackHandler was replaced by CollectingNameClassPairCallbackHandler. + ** CountSearchResultCallbackHandler was replaced by CountNameClassPairCallbackHandler + +* Added NameClassPairMapper for use with list operations + +* Added DefaultNameClassPairCallbackHandler. + +* Added DefaultValuesAuthenticationSourceDecorator to enable default authentication + information when AuthenticationSource returns empty values. + +* ContextSource implementations now creates authenticated Contexts by default, + since the previous behaviour has proven to be confusing. + The authenticatedReadOnly flag was replaced by an anonymousReadOnly flag. + +* AbstractContextSource now defaults the dirObjectFactory property to DefaultDirObjectFactory. + +* Added search methods to LdapTemplate for specifying return attributes. + +* Added list and listBindings methods to LdapTemplate. + +* Fixed bug in AbstractContextSource regarding java version number and base path. + +* Improved javadocs. + +* Upgraded Spring to 2.0-rc3. + +* Added full reference documentation. + +Changes in version 1.0.2 (LdapTemplate) (26.6.2006) +------------------------------------ +* Added methods in LdapTemplate to unbind recursively. + +* Added rename() method to LdapTemplate. + +* Fixed bug in getPrefix() of DistinguishedName. + +* Fixed bug in getModificationItems() of DirContextAdapter. + The method now correctly handles changes of individual vales in + multi-value attributes. + +* Introduced AbstractContextSource, moved the code from LdapContextSource there + and made LdapContextSource and DirContextSource inherit from this. + +* Upgraded to Acegi 1.0.0. Removed classes no longer needed: + LdapTemplateLdapAuthenticationProvider + LdapTemplateUserDetails + *NOTE*: + This means that configuration needs to be changed if using Acegi, + since the API and package structure has changed in Acegi between + versions 1.0.0-RC2 and 1.0.0 final. + + +Changes in version 1.0.1 (LdapTemplate) (5.5.2006) +----------------------------------- +* Now checking whether the first argument to getObjectInstance in + DefaultDirObjectFactory is a Context, and if so, closing it properly. + +* Fixed reference to non-existent ContextSourceImpl in ldaptemplate-person. + +* Upgraded Spring to 2.0-m4. + +* Upgraded DDSteps to 1.1-rc1 and EasyMock to 1.2. + +Changes in version 1.0 (LdapTemplate) (24.4.2006) +---------------------------------- +* Introduced the AuthenticationSource interface to enable other strategies for + retrieving principal and credentials than a hard-coded username/password. + +* Added the AcegiAuthenticationSource implementation for retrieving a previously + authenticated principal and credentials using Acegi. + +* Added a 'cacheEnvironmentProperties' property that enables the user to choose + whether to cache the environment HashMap or to re-create it each time a new + Context is created. + +* Added an ignorePartialResultException to LdapTemplate which causes all + PartialResultExceptions to be ignored during searches, for use when working + against Active Directory, which cannot handle referrals correctly. + +* Added search methods in LdapOperations (and LdapTemplate) that take a + parameter for SearchControls. + +* The methods getNamesOfModifiedAttributes, update, and getAttributeSortedStringSet + in DirContextOperations (and DirContextAdapter) no longer throw NamingException. + Instead, they throw unchecked DataAccessExceptions. + +* The method getModificationItems in AttributeModificationsAware no longer throws + NamingException. Instead, it throws unchecked DataAccessExceptions. + +* Removed the (unused) 'task' parameter to NamingExceptionTranslator.translate. + +* Removed the deprecated ContextSourceImpl class. + +* Integration tests now run using an in-memory version of Apache Directory Server, + rather than requiring a working installation of a directory server. + +* Integration tests now run together with pure unit tests under the 'tests' Ant target. + +* The ldaptemplate-person sample web application now uses an in-memory version of Apache + Directory Server, rather than requiring a working installation of a directory server. + +* Upgraded Spring to 2.0-m3. + +* The build now uses Ivy 1.3.1. + +Changes in version 1.0-RC2 (LdapTemplate) (22.2.2006) +-------------------------------------- +* LdapV2 is now supported. + ContextSourceImpl has been deprecated. In stead the new + LdapContextSource should be used for LdapV3 and a DirContextSource + has been added for LdapV2 compatibility. + +* Ability has been added in LdapContextSource and DirContextSource + to specify that an authenticated context should be used for + read-only operations as well. Use property 'authenticatedReadOnly' + in LdapContextSource/DirContextSource. + +* The Filter classes have been cleaned up. + +* DirContextAdapter has been cleaned up. Most important to note is that + in this operation the 'setStringAttribute/setStringAttributes' methods + have been removed and replaced by 'setAttributeValue/setAttributeValues'. + +* Base DNs are now correctly URL encoded when building the connection String. + This means that spaces and other 'unsafe' characters in base DNs should no + longer be manually URL encoded. + +* An AttributesIntegrityViolationException has been added and is thrown when + an InvalidAttributesException is encountered. + +* More unit tests have been added. + +Changes in version 1.0-RC1 (LdapTemplate) (27.1.2006) +-------------------------------------- +* First public release candidate. diff --git a/mvn-build/core/docs/quickstart/quickstart.pdf b/mvn-build/core/docs/quickstart/quickstart.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c9e37ade43db45d933f4b2abbd4a4c56f2de0745 GIT binary patch literal 54787 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�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 b4#DSN_}nr1{JKlP+9=^4*=%>H{^NfE`j3*o literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/blank.png b/mvn-build/core/docs/reference/images/admons/blank.png new file mode 100644 index 0000000000000000000000000000000000000000..764bf4f0c3bb4a09960b04b6fa9c9024bca703bc GIT binary patch literal 374 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRdwrEa{HEjtmSN`?>!lvNA9*>Uz33 zhE&XXd(lylL4oIh!GZnHecj|txT>yO8>^qY%(y?B;Tppl#t7yOYze#vq#8^aMzDZb YLK^d5CO(feU_df>y85}Sb4q9e0Be`**{8_ndlqdgjcTGH1@5rAvWm>DH}V&z(E>{Q2|u@89nQ znoBxR{K>+|z@W#V1JVle69d~nhv}!E7VV7D!$l=+#gyARX+eW7x`wT@Cg?1ihH6;1 zs$!+xj1Yt%W31lvY*ooh8##Wqt4P*f&)W2{!b0}^@kv>5x2zLQef-gZgT0SEg>hLF z)Pd`+x;*(#oF^Yc1wYmptOPqEY|_Nz%wH;O`WPI6ayD{2tAO4N zo7{UG#GByI2%_fo|5LqMv{6N+A1o@_2o{w)&t4I@GfML`SWvEy;UY+Xf4Xzz&KO;h zV_?(HH9iCh`e$qcdSYD|*JluqH{jv}kl>0fV4tmS-PB+scb>_h18CFP6lf@4^@?vqT-$&hMpcE*)wGd!;~q-Q>IkUnZqz=PVt;M zK*p3gbLK2v%CK~4^3tV1#?q}@8MbbX+PXD)>(;G%_cH9=n|$sZ!?|yxmE{-7;w@N47?rU=3X_NkV zU|o{PnRTZ;lXp4>+)hZU_|Lw%*va*6=<@jI@BP^`_OsZ?pZg-2AaGf|;i2L0<>du@ zeRrO4er03}pLSxdREd>pap^;~&E+}=JYKy#vHnLI=Z$}pPyA_`zG;G~<$`Br2do;7 z$Heivv0AeyJYVI({@6?X6r+V~XS2Cs!|bddDqJz@2lKf$~4dA1c%lfOT+5KMUSWi#X5(9ePxx_W1Bsf2+N)z4*}Q$iB}K{RAP literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/caution.tif b/mvn-build/core/docs/reference/images/admons/caution.tif new file mode 100644 index 0000000000000000000000000000000000000000..4a282948c4c7ed53a2cab4132152c9923f7eb363 GIT binary patch literal 1978 zcmebD)MDUaXJBaHTM%HOBF4+!!hOu3BSpZ2m-o=a4UGyOVRGC@78N*Fn1mTgu^*ne z;*L$0(G;HMj}kXtX=zR2W)oug(x;}vd4|pLf`&qbk&JK3goqWpg18tM4sYt>NNQ*5 zJO1L!te8d4X6?)=pC(QSU||)yt{QYgWZzM1M#kk|4=&{>4cy$*l3-xF?uL{AUz_5A z50}pAFgB%?DF*m>GD)-@{{F$i>YOfT;)C)He}4t3vbN+MR?Ki#WjWH`E;J#5fkSah z*CvDRdtCvNOpI%k6$_&6*{?lq*|st4mU-vV>qgQQdBM7KAGv%{?hRM3li^KVy1S9x zik*=G2sju77$g`J84MYm7@`=G8L}CQ8LAmtfUpM)r!auv95`Ic0D)T>AaF0kUWRi( zcnu8iF+kvR1_*r5@Sfp29R6n*g@Y>uM&o~Q#Rt7z11bTqgJPg?F|Y&xk=0NVR1Poz z%ZNF^GJpYGT5JXL8G!j8gs%ZfLk3G=Ndlpt1L^lb3@Tv^fu#V1MQ=Zj+J`d)yn#^y zss&&)AHzQe7O)6V5GchAW=nvWNNgr3n*%7$3}v$d*=$fYGmtIF2r&nwMieRzvPldo p4l+|5%Ki*wOEI#7?fS#Uz#t7}vjA&lTO_>>KsEyq{Q;^60RYt05`+K% literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/draft.png b/mvn-build/core/docs/reference/images/admons/draft.png new file mode 100644 index 0000000000000000000000000000000000000000..0084708c9b8287c51efa6b40b8d492854191455e GIT binary patch literal 17454 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 literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/home.gif b/mvn-build/core/docs/reference/images/admons/home.gif new file mode 100644 index 0000000000000000000000000000000000000000..6784f5bb01e0104c60e006a2ed525163a380135c GIT binary patch literal 321 zcmV-H0lxl6Nk%v~VJHA70KxzO;Nak3U|_JYumAu6&;S7N@bCZt@Bjb+EC2ui04M+` z000C22)f+N_DFSAmXsV{#*oJWIwOJ|OFii&dHVi$1?u8o=2teG> zb~7H4%2JKz`Tcy)XEL^=cAP+G_P7moKVb2g%$4rYhp9PeRzLz zfrD6ufORfRiHeIHgKCh5l6Fj1nwy-RR)UybJ_V$url+TLhynwsuCA(~Rj;(Av5lgv zwwtvDw^OsE0|vwftp&x&!i>OQw#ml^r_#R6Z_dKdq|~Li{txWV+q;;-NCjLKH8RzODs`x)zXFqfx?o2&^85@jF7AWJN0jDQd6 TqQ?;{LW&$ovZP522><{)rIw!r literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/home.png b/mvn-build/core/docs/reference/images/admons/home.png new file mode 100644 index 0000000000000000000000000000000000000000..cbb711de712dcf06597a3a8a3d95f6fefda1f245 GIT binary patch literal 1156 zcmeAS@N?(olHy`uVBq!ia0vp^%0SG|!3-oLGuzY{7?>FXd_r6WdIS`E6g8)48qP6v zn&afP)GK*ya`E2cmc1=K$9krmo3ixW(yjNl9=mt!-1BqS-d}tE{`vdw@Bfd2(GVCG zA@J$l5_e#hU`+CMcVXyYmGuB}CVRR#hE&{I8+eiLumVSprtq8v761QlTpYPgC-&05 zDJ{&8o?4V1EYqU7+)vOI#yLg7ec#$`gxH85}f}H9d0^ z(^GvD(=(H^6-@Mu^ehxCE%gm7^bHIZl8Z8nODY|5D~n4qll4-I^-@X;^7BgclJj#X z?o!+VG)@a_TxNP+Vo52`JVR3xV`HP#G)t4DL{lRpBV!ZeBojj;vlKG}W79NC!#D3= nHUM?;fpi4`tz|GatuQpzHqbT@s&o4Ulwt66^>bP0l+XkK0lack literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/important.gif b/mvn-build/core/docs/reference/images/admons/important.gif new file mode 100644 index 0000000000000000000000000000000000000000..6795d9a819874ca8b833c4d4993988721489070f GIT binary patch literal 1003 zcmd5)v2xQu5Iutd0|w+k5&Zs3?l2X}aDS zjTF}vbzOHH$Mba8b;e`IbzRT%eBTcOF$gq_YY0gQX<;}D0zVAhF!bZtiem@kQ53ld zc?kJw3X()jQz=b5X{r%YqZCjoWEse^T9yffD1>wGXnD2O6PLX4*vhfx$E#2ysKF~)d4pDz}RBuUaVC4^8)m&+y7^CACN zNWZET0BmraH<<4P+`FFGYnVCKQ9;ZBR3T)qVf|%U7d>simE()pGuDQwHb2TG+MBQ_ zvDDA4Qf1%Pj=rj3vOLN4H;wu(_qSJ$@rtK60JzIpt9fbL=6P}WBLJ%VyrBKnwv9gL z@+koKj|(oEHhf+1r)*HA;C;h{WiuO<>j@AFLq1UdID5A%&u-GCYUTM&`rF#A&P{r^ z_xRIVhqhjHEgv=NdwEBz@%x57)QYSpY=k4uyJx0tows>V2jGshqK7Z@I~q3m5W5c^ zZt#o~c$MEO+~MK7@*>Lh!0mEumT-7-ns3eG+lQO)ySz&7zEB7GF&sm<)jwePAZ~s8 Q_s=i~Dg!i1^#?fn4NukiE&u=k literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/important.png b/mvn-build/core/docs/reference/images/admons/important.png new file mode 100644 index 0000000000000000000000000000000000000000..12c90f607a1b27ddde0a7d922ae255e8c90e883e GIT binary patch literal 722 zcmV;@0xkWCP)Gdz_qOoSb{LwMwNgp7=gl$4aErG%}mjHRWNrKOy`y@b8JoTa6ut*xc4t*y1SwY|N)#>U3Z&d%1> z*52OU=jZ3|@9+2b_y7O@tTMkk%M`~Kg@u8&dg_P^_0l3yQb639!jLZt^Lx<-O17UeeJ z-|=!77W(jGx&e#?FOku-gKofoU0$~4M+dhLFueEauP`}l7LV=;lsOdn%WHure=x;k`m0(bF&MU#) z-qv#^n8(MjB|ykioqII#+`g4no-MU=BK|Sahu_3M_-d*=7hq=~t?^}A)G7 zbairN0An*{V`DL9V>K}|HDobmIW;*pIW=W9HaTQ6F*7kTGI9=S@Bjb+07*qoM6N<$ Ef=i}M4FCWD literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/important.tif b/mvn-build/core/docs/reference/images/admons/important.tif new file mode 100644 index 0000000000000000000000000000000000000000..184de63711510990ab0a8f483c274d2f11596cef GIT binary patch literal 2020 zcmebD)M7AWXJBX$dk|ousK?9JTDdL8Uq@YU@bJG?*jxfpA<|j-!WmZOftnGps z2`M(7+NV;37GB736>XW?tSpqXE>}~nv-Ps%oEtMlIQW-6+n$q%9M;|CK z_#F#ZV$1tJGvEmalTiEz3u}o#Tuh>mHiUOeO7JnuKG`_As3Jnn>nKNe(yJ$T`WW1e~GYvWZ4Wap86IdPu0ZRZU zVEzvR=YI)c=}-+U1EPR<3b=fz29^QIz>)z}8Z2c{1j=VaONynyvH(;CHn0?60O~UY>I?#w0~|oMCXfdzd#ZtD0Rym90=efsxO4)k@&c9v z5Z5Y>mIBlY0dHWCG6FG-=41HBzycNl3Ie5=!E6Z-6N$|PWrOMuW+jIb=LGEI8QAA*>a1B0!;`T|n7uj>pG`jtu{evhJMNxcGSe4+aUA4;zBR~SJyxQRUcWR@_F-^jeWYB);NUiculZ3jmx6N0 zeB+OSXDhy@IoCx0FO{tph>zHm!T!klb*W-Q^N$-zxf}}=>~`{%GD&ps=U<*F$^Pgw z_kzS@O+PsF&r}G^Pim1AepDS}@Ytzl3Ts+UM}N_=nLk9*dAj;P>|etE;is#v!2PUi yKjIc2Q@7UKxAw<}hlktwFXd_r6qS{MX+1QdG|HK%AA z&M|bFA9tAudUsBZ|kvp$Id-JckTVP=kK4t z|Nj2}C>RZap&SB+ixVCKvjk(3x4R2N2dk_Hkkjkw;uunKYwyH|T!$5ST0$SiY~H5U zR{cLbR)lT!-5c|b6rGCX%N?2P<`-xxUu`gZx87&*3!~4wH@%p0)WuYX}UBIC~8cAiK_+plk!-xP|nq~%Rem@g?39waS$dFIAw`2be&`&H`~ zup7N@y!g@N+18CSR#dLnIlgB9pXN>WRz-=n38I0oRnmeW@(a^WRPZVkYb)}kZ6%y pT0bWnsEZGzD*$LMgRyCap{cfkwt-Nc+b5t5gQu&X%Q~loCIEjWXuALa literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/note.gif b/mvn-build/core/docs/reference/images/admons/note.gif new file mode 100644 index 0000000000000000000000000000000000000000..f329d359e55c7ed753170a6f04fbc0cba1e1e565 GIT binary patch literal 580 zcmZ?wbhEHblwgox_`=Ho1YTZVK|w)LQBlds$=TW2#l^+d)zvL6Ej>LwQ>IK=x^(H* zty}l*-FxiVv2*9nUAuPe-o1OzpFe;9{{8>||AFElBX9%7pDc_F45AD=ASEC>8Cd@% zghGJlBo$966GSzJUe!6ZN$1q35SXB^-zJAkEPj)pXq*a$h_ zXQ+sXsjhEmXhNv37hJ?OaLOg1X|~+1jSyWR(o`3y(c1;Ai!}%$em*Glyc1BwlJ~NK zuZpLThNs9@k1ImH0$Wth2s`>W>73rA(RIXT!4{9MLpCC+wq2hkd_`O*d~)g9;?U*N zC}OI4M1Z6Hl7Y`-pGiWx6O;l)uFCkcxOF@EUU3NwS>oY6#RI6O?U2byr$d1v9)TjN znwNw$j~n?OWwbpf2zH99Zo*Lqk)s;^7gg9U27sNV>(8nyV%m64QG)9cM8x-WNa$(j zzz`MJAQq_8eSmmjy7}|Lu@zf kf(CKug-JSrEM6BktMtGlBGk2ik;bV{KB5sIO^OWG0Fd3m^#A|> literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/note.png b/mvn-build/core/docs/reference/images/admons/note.png new file mode 100644 index 0000000000000000000000000000000000000000..d0c3c645ab9af6318035b026dd86944b9ddc9114 GIT binary patch literal 490 zcmeAS@N?(olHy`uVBq!ia0vp^5+KY0Bp8m$B&h%?rX+877Y2q^y~;)m42&$EE{-7; zw^Ao5PC9JB<666~wbh%Oo7tJ0_rGWfi({a(qjR96>_P4H$tJl=Pxd^1p6`8M?=jE( z!hchf6<8Mi`NHe6`iia69>!lxe4C2-S>+Wvj;l63k!MNSb9|1+f-i>irDi|a^uLl% z^#-f2#pQ2lC%m~9m9JWJZL3|T8FRX>A78<>cU{Z6XGFG336=OLGkw25|29|RG_k%U z>j|M}Ih2i#-w+9_WSj8)*j_s0*s+gA3;Sh&ja)bx!VF_Sf>*se@&+kCgO zr+R@?&ik*cqgb|1nDsf`Smu_<(#~sYRz=fNIgWLC7l}#=|C@V!^7^u?J-&S9hp(U4 z$p4Zq##20*5QGIHvwJ-nxqBD;h literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/prev.gif b/mvn-build/core/docs/reference/images/admons/prev.gif new file mode 100644 index 0000000000000000000000000000000000000000..64ca8f3c7c6856d17625615c7845d9adf8b35e6d GIT binary patch literal 1118 zcmZ?wbh9u|RAW$Q_|5jIb=LGEI8QAA*>a1V#C72?E=btEDjqN9qpDd4zl5RF#mYJf^(P5PkwoZW(JQ{ zF_D*4y!wR%9~DOmG#sD7{_&y;#{p%(<{xE#Uw&*>uv^H)Skbd{qJrHLc7}?c9UtQz zYXTT6YHoZvu)pcYrmCD9p$~wJBmbf!%0P@OYB}B?fNF{YnIe9ySJ<~ln9<}1LcYr;@((b@HMtAlPLh^PsKR!G>+|CbXxWHL3#->LwIhe?U cW`4ULC`3qLrpm*X4$b9ricTFB5n!+e06MwKH~;_u literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/prev.png b/mvn-build/core/docs/reference/images/admons/prev.png new file mode 100644 index 0000000000000000000000000000000000000000..cf24654f8a9d6826bf5ee3f6b640d0b34f44d2ed GIT binary patch literal 1132 zcmeAS@N?(olHy`uVBq!ia0vp^%0SG|!3-oLGuzY{7?>FXd_r6qS{MX+1QdG|HK%AA z&M|bFA9tAudO|H@7THL=dQiK_Wb?x_ut?D z9|fZ!FvLS(&zmEwfLVeu$=lt9p@UV{1IVfNba4!+xV3fSLB2x#g-7SkiDHi<0%5c709k4@>c%U5PUn6XiNrQ7+$BX-K(BEj>;XDjvwZcIF~ zikDl%a$|T|&B@5M-I14sdv^1r*gkp|z3k=crb$1i`yH45`zBpRc(KYI-u^rPzjiy@ zGH(5%+Qq}Yvm59P=MvY5lHmNblJdl&R0anPWlhiA#Pn3(#PrPMYy}fNBRvZROG|wN z3w;Aah2)~l;*v^-+{)sT%w)aPV!f2og8aM^z2yAdiMtec0FBcE8<&}$msnB?G|wQ- z!aOlC%^=CZB-O|yDbdi(G|ec{Bq=S?EGaqB)I9sbjHy6fd>~x`Kx-L{O)CsdwGFfl UgzDTr0c99GUHx3vIVCg!0Q2->tpET3 literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/tip.gif b/mvn-build/core/docs/reference/images/admons/tip.gif new file mode 100644 index 0000000000000000000000000000000000000000..823f2b417c797bcc5b5af0d86034bbbe68a9c5d8 GIT binary patch literal 598 zcmaKpzfZzI6vwMTVkAa>9EomJF@dB^cAyaw8YzNW>;V&-Ax?~pOgK0Qkwp3j7=?eJ zp(Bd}iDHPQ&cwK|BZjz$_padR<(hoox1W3W-n*l8G9sLBGh57bgCWc1aD#QQmI@nS1Ofyy{@WiyWQ4xz1!{fdcDD5FdB`<y3!k zEzMQ}>2XNH@2x$azJrio4y76u%{aaSQ8iI0-$_v{WV^70wC+frSXf@6n4D`V#?Bj* zZt`izA4lq-+})D%wiNUU4VVF+%2sLK@TMg8DV7ztp%Z(?@L;tQU1CsK&Qwqezj{Cq zSwCP?&P}GNbF_4EK-+twGeQq!I#zP|Y+{WP*FI?V_Kf=>vV0rySc_v6yVepU`O=gc N_rd2$!U|dD;1^e0&sYEe literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/tip.png b/mvn-build/core/docs/reference/images/admons/tip.png new file mode 100644 index 0000000000000000000000000000000000000000..5c4aab3bb3543191c360387c4af9a3cbaa051345 GIT binary patch literal 449 zcmeAS@N?(olHy`uVBq!ia0vp^l0YoM0VEi-?r};1DW)WEcNYeRRlUkaK;CUn7srr_ zTcwkB3m#VBagbEhuIO1a$!w|j^feEHv@=+|woj32oF*6|l<3XH$!T}Lao)e6jJhoQ z_vr?OZVprC|7iZ8xaicUR0b&zR-JdP408=+?zC0Rn8IN&QLTWhMIcU0X8P7uUN4WB zR_?a_;&p>ll{>(OzfJ4N{OX={Q&zNgO}j31DC;|ya^r9Fjd348&p+!^cT+^sws+=& zCu*T*mRNK3PE;$BNx$Fa_9(Y=&DoXLMFRd#U31O)<`W^F&-o=xbIqK-c?mr!bmn>! zky6TW;ML~4nXw$T{yra=OteL6G!=B;0=?;6;u=vBoS#-wo>-L1;Gm(b>6x3Dp6Z*J zo|&AjV4`QFXQ5zesc&GRZ(yj9T$EW{Qt6OeSzMBtte0A>mr`1gpI4%noS!>!m*Ngk zP=bxiOwUU!DFvFBnrfM1VQy}0X_lO7WM-b2nq**TZkb|inr2~`WR#dJWc*7VsEZGz hD*$LMgOO>4k%_i}w!yxSoa=!y44$rjF6*2UngF(YpwIvS literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/tip.tif b/mvn-build/core/docs/reference/images/admons/tip.tif new file mode 100644 index 0000000000000000000000000000000000000000..4a3d8c75fd58190ceae8d6ffa621ecf7e925c3c1 GIT binary patch literal 420 zcmebD)M8*_WMF8pe-L1yJdc;H$tf{F-CELXO3;xcp67e?gucfex%0l_j;Y3!bk~Iw z*}inn73j9T)O}-3=fCGcYZHT(zR^l}-s<&G=c&#%w(qtbzQr9{)4X|tE-LgF&v4l4 z)p%aZSR=ZsmwRfU)`umDZ@!1L>ZTZTei3kDV32yV!Q3h+-nMlqyV>lRzWV|5XS|!R zJ9po!|JC=_hMz9Ex3Ove_O<7Ki^+ZG3OW)0HK&YExa~knT17|Dw`}IsahgrSch^-` zbR3#}CTEJE3FpcFHhEeT>m*%ClD6O_#XWHUqAtUxv!lnpXbkP%`INR23xxENF% fWR5tL{SwHQVq^u|^$Ez9hO&PF*|tb}fm{Xvme+K0 literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/toc-blank.png b/mvn-build/core/docs/reference/images/admons/toc-blank.png new file mode 100644 index 0000000000000000000000000000000000000000..6ffad17a0c7a78deaae58716e8071cc40cb0b8e0 GIT binary patch literal 318 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngf!VDzk7iOmbDT4r?5LY1G0LBeqssYGrXgF}- zKtn^rf1vn(hW}s+NCR0w;4iG^2^42c@^*J&=wOxg0CMC!T^vIyZYBTtzyH6zKuy9A zentg0F+qV0g#~P97#OBpaJrNsxA6f`rE`gEL`iUdT1k0gQ7VIjhO(w-Zen_>Z(@38 za<+nro{^q~f~BRtfrY+-p+a&|W^qZSLvCepNoKNMYO!8QX+eHoiC%Jk?!;Y+JAlS% zfsM;d&r2*R1)7&;o@#7ik&>8{Vv?F>U|?x(ZfKHZYGz`bmXczeoR*Z-Hs=yh7cWRx f0MJ?nL(>XNZ3Ars^Rf>h;}|?${an^LB{Ts5OHX0g literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/toc-minus.png b/mvn-build/core/docs/reference/images/admons/toc-minus.png new file mode 100644 index 0000000000000000000000000000000000000000..abbb020c8e2d6705ebc2f0fc17deed30f2977a46 GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngf0VEhsJkjh1QcOwS?k)@rt9q4-G!sMP)HD-wQzH`-1CumMgJctv6pLi@6hos# qqtv?{|7HPo@q%;(0Ig*(G_A1IHqbUOFZ%#8j=|H_&t;ucLK6V~f=xvL literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/toc-plus.png b/mvn-build/core/docs/reference/images/admons/toc-plus.png new file mode 100644 index 0000000000000000000000000000000000000000..941312ce0dab168e0efcc5b572e387259880e541 GIT binary patch literal 264 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngf0VEhsJkjh1QcOwS?k)@rt9q49T#T`K7w7|w?rspM=lmg95OfodLFfd9rOi4*hH8wIdOfpPPHA_l1 vPBO4aOiebg{jIb=LGEI8QAA*>a1V#C72?E=bjEDjqN9qpDd4szjmF#mYJf^(P5Pk#A^PL<$Q zI+;RG?K?R>ZZ+XJpnRtJN17PRgOAG`Ypgh#KAaFZVBhwmiI3&M#ti2gFIJ`xI|LdI z)N_AywPiSv9NPY48`Fms0t^T1xjuG&jrgXxZ|i>{CW9aPigr7S6dxV77JO*m^5e&) zo(}&F0j>Y}9vSZmQL;N0Bl*v`gWrf@#t#YRf*HM~)&2b!mjeThEhE5z G!5RR%al9%3 literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/up.png b/mvn-build/core/docs/reference/images/admons/up.png new file mode 100644 index 0000000000000000000000000000000000000000..07634de26b325b09b6686543e3743ec58426e64b GIT binary patch literal 1111 zcmeAS@N?(olHy`uVBq!ia0vp^%0SG|!3-oLGuzY{7?>FXd_r6qS{MX+1QdG|HK%AA z&M|bF#=*s&OJYO?ftdq@1MW_{{H_c7!85p5(1lc z&#VJx3C1LEcNc~ZR#^`qC(YBvF{I+w)Ct~v2NXD1b6>NwI=-vFd2Ozz_LBUAZ6_V# z0_-2ED|^IRKipvRGe~yg+2{$+0#aW-GMHneR${}Twfy{=Zu8*Wewig#Kj<-8yG4i7iAWgR668V7MElu>!lX!rIZ%r=auLs=jTq` zrMLrVoEF%)%=Em(l2V{~M&{;5#umm#78a?N$;qjPhKZ(zMyW=L7Ktfo#^y-|**sf- lni%*%x&naKG8mgy7@BGuXd4LCxqSjjdAjiitKf)ghIjv$xRfA5@!fkDH{bMrpZER!`M%%z$hqF& zRTo^?=>a_=-~^4}0c5}joCOph4kUmZz-=G{VS5-sP2q6X`MO`-x!!*rW ztyZts8;wS@*(?@mpZ^}uZ$}#tfY(x3o*?Xoo>h`d8|jmC(W5ivVgErE1sEKfo%%8K zZm_%KW2+i;iCOE!&3{olD%O0Ac=Thp(EZgCUC z>m{!ZyCyqrXB4)SL^$CI^0ECR$tT=P*F@;1lfy%=Q(o%DDkk-3k1-hj`YC2`iO!;PB{{!F!o39xr+d_Vw5fMTEwpb-s#9q<5Nzz6gIepyU?Lctpr{ZK zVzWaWPAC)#17S8h%;AIuf(Q_yeIybEqS0s^i1YdJcsv0ln9M{Xkpz;-I_^=P))~D~!!Hvpq{Dl8O{rN@cECkt>#DncX%I(O&3i_YgL-$m$VU zT3cLBLS%M1`o{TBX}S|Tbhc)vk!Yp)%rdDd&my(RPsxl%lU$)tC?(1~WuEwClUQn! n$Q8O{Mxt@ukkcT{K0> literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/admons/warning.tif b/mvn-build/core/docs/reference/images/admons/warning.tif new file mode 100644 index 0000000000000000000000000000000000000000..7b6611ec7a1980022c11ad6877fedf32f41b3df0 GIT binary patch literal 1990 zcmebD)MAieXJBaHR|qhCa3Ybt#fe?OBZVhOke}_5p}~zDOYXJdOmf$P_Zjir-v(f(n1LVCeE22NskqJ)$I5g z8XP9S{LzviJ+sf*L4iXe*;u-Tjq%lq8EgzJ!Hcg+?M+EyWoUYBc3S038aqStx-zY! zDFTdKac@c|p3q>CIvAgpWRM#aTC%3+U_*~obs9PKLA zwdWf7S{m)1m+z3c?Wmx@z`-EEAipvhp!;Kbm?5X2D0kj+rcP|eW7(8DkV2Fki5IhEj=NKUH8UqO41H$KE_?`g-zXRd_Q8-9KfPnz02B(N>pejpX z>SzI`howNaA&_POrkNgK+E@xq2Mpl!zyMB5$ACOgn$iTTxCSKe0kI-51uy_p+j}7S z9f(I$z~Bf0U{v4*qwzmD;)zDC@CHUcBM`%AK8Ak`EMO6Es$gUWvn4>Pkl0L6HV06g z8Omk_ve}?)W*}RT5n>KVjVM$cWRn>L~%oSd9oTwL7T+&nxy zyu7@8e0==;`~m_3f`WoVLPEmA!XhFfqN1W=Vq)Ur;t~=Pl9G~AQc}{=(lRnKva+&r za&q$W@(KzHii(O#N=nMg$|@=KYmvnwpwgT3XuL+B!Nqy1Kf0dV2c$ z`UVCDhK7blMn=ZQ#wI2vrlzK5W@hH*<`xzfmX?-QR#w*5);2aawzjr*c6Rpm_6`mX zj*gB_PEO9w&Mq!4uCA_bZf@@G?j9ZO-;?s%`Gi0t*xzX zZEfxC?HwH*ot>RsU0vPX-90@$y}iACeSQ7?{Szikm^g9bq)C$|Po6ww%9N>7r%szT zZTj@-GiJ<~IdkT$S+i!(o;_#IoVj!7&YL%H{`~n17A#n}aN(jwixw|lykyCerAwDC zTefWZ^5rX5tXR2n<*HSyRoH%*%du|NsArKUo+Uftf%D zloLRCf`MZx!~eg#-8I=+I4mvh{{H^{{!R%71_cjB28W*F|L=~16e_0!iH^d4K?Vk| zb9al`Kw>>fAksk-B*h@W3^J6Vpa?9*ApqhqsDq^%6u@FoDMJacLWtCX7jZxbFc_?2 mWngHK`2KIMH&~K^;fMv$8T+eyk`x>!G+)|V|DTD8!5RQaJghJP literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/1.png b/mvn-build/core/docs/reference/images/callouts/1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d473430b7bec514f7de12f5769fe7c5859e8c5d GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQC}X^4DKU-G|w_t}fLBA)Suv#nrW z!^h2QnY_`l!BOq-UXEX{m2up>JTQkX)2m zTvF+fTUlI^nXH#utd~++ke^qgmzgTe~DWM4ffP81J literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/10.gif b/mvn-build/core/docs/reference/images/callouts/10.gif new file mode 100644 index 0000000000000000000000000000000000000000..fb50b06d150685fbc333906361e32afbf7684993 GIT binary patch literal 968 zcmV;(12_CfNk%w1VGIBa0QUd@000010RaL60s{jB1Ox;H1qB8M1_uWR2nYxX2?+`c z3JVJh3=9kn4Gj(s4i66x5D*X%5fKs+5)%^>6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LV0RR91EC2ui01N;O000Q30RR2_$0-K}2ss$t`}gnPyBrWO06>re zLJV*J{^dYWt5yncz0OHVaj#T}cmHbDss&(<}jYB?hWAjdxpF-idd;H(xSYY-?o1HkBC4gkG4 qLFjeQT8tF;{=Lfq000002mqw;_V3@nf0S|{K#+pcy?_4#0suRd literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/10.png b/mvn-build/core/docs/reference/images/callouts/10.png new file mode 100644 index 0000000000000000000000000000000000000000..997bbc8246a316e040e0804174ba260e219d7d33 GIT binary patch literal 361 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQWtZ~+OvdJMW|Y+^UT?O-M{rKJsmzxdayJ{ zDCQA!%%@7Jj$q%-wf8e0_jRx8Dqi$}^?K=?6FriQFLv>>oc^CE+aVHhW3=nZ+fQ4!M=ZC7H>3sl|FJr3LwU zC3?yExf6FO?f@F61vV}-Juk7O6lk8Yg;}bFaZ-|HQc7Azopr01?u8M*si- literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/11.gif b/mvn-build/core/docs/reference/images/callouts/11.gif new file mode 100644 index 0000000000000000000000000000000000000000..9f5dba4f8df99fff77747affe9c6c03492ec896b GIT binary patch literal 244 zcmZ?wbhEHb*_Mn*=~*4EC>&cVUKsi~=%nVHqq z)opEU{r&yZr%#_dckZG^i`K1Mw|VpC9Xoa$IdbIe*|V1}UAley_T$Ho-@JMA;lqc2 z|Neo@1p~#OEDVf5mJWyqvXgP}^2;9)OfJm7I)A)~{gLS`_d;ZPy>0S|_X3{XE z)7O>#600DeuDZ?5tOl@ql94%{~0TwC?8m~C^ZqJRG}m@H-L1 z5L@scq?{XUcxG{OP9jig5ySQaTl#^*93bKF#G<^+ymW>G($Cs~V(bw8rA5i93}62@ zzlJGu&d<$F%`0K}c4pdspcorSSx9C{PAbEScbC)|7#JBmT^vIy=9KoYUDZ+`aP)jU z&ny=ErrK^#Gw!AcR}pdfMERuV^@&0$@(#^6b8c@rn^6RWX3pUb z4*6@PZ+H0#u=rjsXzS?6n6*sBGbHqGTU%mCsH?n#%j;eD^2}qe=iX*J@VQ3BRpz+u z{PX#N(^9X${`$90+;!pWs>o@z_n8G)7Uo7PJz`jrS+)QE@=PWHmc~UIw=WmUe73o7 z>^bR(M752aYoNg~ozu7U7&{(U>{s!;bn#f?ItjL^o`e{*EOQHqO;ccnz9hLK5@2cAyw@AaPFL~Cp#02|E|4xeQteNtB7waMs QVCXP-y85}Sb4q9e0GRUFb^rhX literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/12.gif b/mvn-build/core/docs/reference/images/callouts/12.gif new file mode 100644 index 0000000000000000000000000000000000000000..a373d0b4f4203dc34d00cfc337fce3c6940ac0a5 GIT binary patch literal 244 zcmZ?wbhEHb2m08OKLyy@67BNn1 zV`M$b;Hb*j&z8jCVAzz(*|0d1*}-svnJ2^2O#T7^_7xgl3o~c6fy_O;6lj#|@c@$< P%QAt6&q75}91PX~p`BKN literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/12.png b/mvn-build/core/docs/reference/images/callouts/12.png new file mode 100644 index 0000000000000000000000000000000000000000..31daf4e2f25b6712499ee32de9c2e3b050b691ca GIT binary patch literal 617 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1SD^YpWXnZI14-?iy0V%N{XE z)7O>#600De9$%>2LVd81Yeb1-X-P(Y5yQ%LXFPyHJS9LOm(=3qqRfJl%=|nCVNOM5 zpg0#u+&RCXvM4h>ql94%{~0TwC?8m~C^ZqJRG}m@H-L1 z5L@scq?{XUcxG{OP9jig5ySQaTl#^*93bKF#G<^+ymW>G($Cs~V(bw8rA5i93}62@ zzlJGu&d<$F%`0K}c4pdspcorSSx9C{PAbEScbC)|7#JBmT^vIy=Cn>wTzx1(qV@bS z0hYvspf(--lM>otrqbK$7p{3DzJ|+KN8%5ows)AI?zWk_n>jwEHXrTJecpEW_0xL= z?}N`*R`T~d2{AN${y8T#GEn4hUb&52^}Op@TW4{oc)A6)%$5=G}h# z?O{QLj@aRcAIf&y&OiUN=H2gq=_}V|pWfuReDV|{jwXw~>#w)I|9$3x|moMMCb?eciM{nM|`S|hUzkmNgCL#gF zpDYZFK)w!03}hz*YvF=rnZlC|CNImJwM-Kz+OQ~d)-w*#q=p-*7PHz|r?uJeim=Ww zV{C0@=>Ntrnaz(OL9u;eM8lFu#sozHRo??kGr1dh7*7~9Ey|P%1DVSLG^)+=Sc1ij PWtl+3XQ84f4hCxgsBBm& literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/13.png b/mvn-build/core/docs/reference/images/callouts/13.png new file mode 100644 index 0000000000000000000000000000000000000000..14021a89c2ed3d4881afea6e3a315bce4f95efce GIT binary patch literal 623 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1SD^YpWXnZI14-?iy0V%N{XE z)7O>#600Dep5bGK9wD%hYeb1-X-P(Y5yQ%LXFPyHJS9LOm(=3qqRfJl%=|nCVNOM5 zpg0#u+&RCXvM4h>ql94%{~0TwC?8m~C^ZqJRG}m@H-L1 z5L@scq?{XUcxG{OP9jig5ySQaTl#^*93bKF#G<^+ymW>G($Cs~V(bw8rA5i93}62@ zzlJGu&d<$F%`0K}c4pdspcorSSx9C{PAbEScbC)|7#JBmT^vIy=Cn>w>~AWNX^a2R zbkveVY|45D7UnZ&JtjPwvdCCscZp0EA*0()#GOw)UH4-^&)y^E*4%UC)*|J}q_Ss;tN`nd8$>x9$_Xb^O2EpX&@C ZI46EzbLxq-voTO7gQu&X%Q~loCIF_C`w;*D literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/14.gif b/mvn-build/core/docs/reference/images/callouts/14.gif new file mode 100644 index 0000000000000000000000000000000000000000..6d6642ee96d73aa5b2efe17277f15c63e15ff643 GIT binary patch literal 244 zcmZ?wbhEHbgv_2w{G2f{P^+Pw{Jgw{P^$RKag>#K=CIF z10zsK2P6!#lYzA`VOnY=t7jyToEFN;z;JkSYGfEE2V(<6l11b+S?_5KEX!CT+8F(( zvRqlp;LjGuAfVc->c}`bkwHN5baLRK$*JrYWEePfJPT4IRYB%D08MZc1e)(ZEfr|E OB-C)A`C!DsU=08}Mo-uP literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/14.png b/mvn-build/core/docs/reference/images/callouts/14.png new file mode 100644 index 0000000000000000000000000000000000000000..64014b75fe2e84d45ed861974c72462727979360 GIT binary patch literal 411 zcmV;M0c8G(P)!ax*-PXaQ9e~6^e1gu=a6a&KSz}bR`+prYG9ayB$BDjWGfIE;t#wl!+ zR3S(jA%y#i_@eOOedXoc%RQe%L;wH~k+s%ZI~)!<=dD%?4MaplaU9QPGski2q3`>r z(}{j@0a$CLl+)={2vLWml*i-oa5#J}DW$gCZB~Z!(!M#)2St|1_V^0qpmCrBof=Y&NUas@LmfSw=)4B4f;8Fu)(eFsv24 zJzXxBrayquXcR?Ji+1bU# z#kIAy&CSg{Jw1~rPo6n*=86?7wrtt5XV0EPhYnr2a^>dDn-3m5c>DJ4moH!b{rd+p z6b2N3vM?|LIXWN#kev*yg%7sn&Q|rkxh?nhwpdmMhK9|#x2N%XFg5sYwz$1bx0{Xk z_GO0a+gN$Os>~K+Jja&BAYj`ToN;JVE|Y+*Pie-X&AB`WbeJq+xeIcm-9YAoOlVUC XntyRyF3|Aku*lmW!=WgegTWdA>{?yt literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/15.png b/mvn-build/core/docs/reference/images/callouts/15.png new file mode 100644 index 0000000000000000000000000000000000000000..0d65765fcf13dcfd87914744dec8bda115e4adf1 GIT binary patch literal 640 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1SD^YpWXnZI14-?iy0V%N{XE z)7O>#600De0j~t#c`vY#Yeb1-X-P(Y5yQ%LXFPyHJS9LOm(=3qqRfJl%=|nCVNOM5 zpg0#u+&RCXvM4h>ql94%{~0TwC?8m~C^ZqJRG}m@H-L1 z5L@scq?{XUcxG{OP9jig5ySQaTl#^*93bKF#G<^+ymW>G($Cs~V(bw8rA5i93}62@ zzlJGu&d<$F%`0K}c4pdspcorSSx9C{PAbEScbC)|7#JBmT^vIy=9Eq_Jl&Ka(%QdX zh{H8O%#_7)Tc@t$mM`p4(Ne7omR*~(>gd8_8AZH{=3ms$Fmzm^yL@_+(#aQQ5>7QW z>3g2fIsH(ugM)!V$x4Rr_+!J_XU%4xbz0aE;^N{m@42Z|@0S@TQ=WbP`TMV5Ok;<| z^Ihv+@6tQ{sciRF9dD7Nr=KobwJJ68zJK$<1Pd9rz%4O)*;}Jzj&~nTGMecz>B%lV zK|`fmIc8mp-h8iSXiGFW=C(L+XH4DRxZQX87^-dLuD>odo6YLT@Sw)dfBEIG)v2@6 zR)%mL7GRj1x-&v&+2q@A%a&h0`Lw7|#(w_!tgT!PoJ|+re`l6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LV0RR91EC2ui01N;O000Q30RR2FM=1vc2st?2`}gnPxflQdqzC~3 zK#X($?!^EQ!rHla#vlNQagPxI0Hv$}2yu@Q004|)006*f4*&q(Y5@Qc0|5X40001p zac>R)001^@s67~R_g00000F&F^=u;$*F7%?ya0Kn*83;>+Y qojZ4K4}ci={+){f00000006`|_wV1odyHZrK#+sdy?_4#0suS0gSs35 literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/2.png b/mvn-build/core/docs/reference/images/callouts/2.png new file mode 100644 index 0000000000000000000000000000000000000000..5d09341b2f6d2ea2d1d5dad5d980f14b4b05dfd2 GIT binary patch literal 353 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQxaY7e*=hH)_rZeB4|imU1$R#1`!P>&$poQl;nzm}mD5ZFopaX|GsS%q*{P~< z;WtmO%lhToBL0i}yfkaOt?EN=nkLNGuU`ywhI5H)L`iUdT1k0gQ7VIjhO(w-Zen_> zZ(@38a<+nro{^q~f~BRtfrY+-p+a&|W^qZSLvCepNoKNMYO!8QX+eHoiC%Jk?!;Y+ zJAlS%fsM;d&r2*R1)67JkeZlkYGj#gX_9E3W@4U_nw*@Ln38B@k(iuhnUeN2e6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LV0RR91EC2ui01N;O000Q30RR2FM=1vc2st?2`}gnPxflQtl zK#X($?!^EA!J9R2&L9AYagPxI0CUbEfbee(004|)0000X1po;D)&Kwy0|5X4006W) zXAJ=W001^@tB7y$s_tQG(OF&F^=!026!9PbDL0Kn*83;+;X q-MiNc0e~3y{+&w!00062006`|_wV1odyHZrfDnVyy?_4#0suROfV)2c literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/3.png b/mvn-build/core/docs/reference/images/callouts/3.png new file mode 100644 index 0000000000000000000000000000000000000000..ef7b70047158970cf4e09f1bab2954d39c2d596b GIT binary patch literal 350 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQF0kK0(Y1u|9Rc(19XFPiEBhjaDG}zd16s2gM)^$re|(qda7?? zdS-IAf{C7yo`r&?rM`iMzJZ}aa#3b+Nu@(>WpPPnvR-PjUP@^}eqM=Qa(?c_U5Yz^ z#%Y0#%S_KpEGY$=XJL?(l#*ybuErX#^g`ttQfwn6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LV0RR91EC2ui01N;O000Q30RR2_*Gfu?6iQ0{`}gnPJtY7D07wA= zK#p_&{wVRjGG#0000G0|Nj6TFybZ zHwOR!00=Mu2=Q)CihGPO004kM34k&G{{8#+uM_|PF-idd0001hdvgE)0Kn*84gdfE q0Pwa30Du(t{=Lfq00000005-$_V3@ndz@lGKoEn`y?_4#0suSSP`y|H literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/4.png b/mvn-build/core/docs/reference/images/callouts/4.png new file mode 100644 index 0000000000000000000000000000000000000000..adb8364eb5d21ecdd4086e16110b62ddcb42aa4a GIT binary patch literal 345 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQ3r>K)tuC)r#2`iJ>Prt42#Ndx#Uc~1)>aw z3jE@Q4|!9Z%lVv}- zc=48cF7H)t`(Ck`^+mtha~Np7bBSw2NpOBzNqJ&XDuaWDvZiNlVtT4?VtQtBwt|VC zk)DNurKP@sg}#BILUK`NaY>~^Ze?*vX0l#tv0h4PL4IC|UUGi!#9fLzfW~Qojmu2W zODrh`nrE42VU(7fm~5G9U~HM3l#*m_WNcxOXkuz6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LV0RR91EC2ui01N;O000Q30RR2_$0-K}2ss$t`}gnPyBr8oqzI7$ zK@4yI{^bCW<{Pv;Y7==^Ow6F-idd5aXYc5Z)*O0Kn*94gdfM qy}MTm0)Q0v{=Lfq00066005-$_V3@nf0S|{KoEn`y?_4#0suQhF}%G1 literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/5.png b/mvn-build/core/docs/reference/images/callouts/5.png new file mode 100644 index 0000000000000000000000000000000000000000..4d7eb460021e845981861d77614539314f553993 GIT binary patch literal 348 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQEX4g z+-vfUhb0A>b04=Im{6XiQd1v%r%>h0$G8U7E1If8OQ!N~xOYY5h0NDT$p9(iZ?Q&e z18-(+l~J8O`)kc}e&uL$eW&>P-#`~Qm$*ih1m~xflqVLYGB{``YkKA;rl!p+yCFkc(+@-h!Xq*<< zxXkpt#FA2=d1VEBsYynrsitN|Y01eJ$;p;U#>wWX2KP5v&I9V=1L+C? fTFYQ)RAFeOZJ=$?lDoSWD8u0C>gTe~DWM4f^}upZ literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/6.gif b/mvn-build/core/docs/reference/images/callouts/6.gif new file mode 100644 index 0000000000000000000000000000000000000000..d39640707444f5f67b2cd324143c2d6b40d27a40 GIT binary patch literal 968 zcmV;(12_CfNk%w1VGIBa0QUd@000010RaL60s{jB1Ox;H1qB8M1_uWR2nYxX2?+`c z3JVJh3=9kn4Gj(s4i66x5D*X%5fKs+5)%^>6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LV0RR91EC2ui01N;O000Q30RR2_*Gfu?6iQ0{`}gnPJtY7DU`PP~ zK#p_&{wV<}SN&x`ij1T|-F-iddu;yKe5ciY-0Kn*84gdfs q-MdE!0Du(t{=Lfq00066005-$_V3@ndz^A0KoEn`y?_4#0suR2o4}_4 literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/6.png b/mvn-build/core/docs/reference/images/callouts/6.png new file mode 100644 index 0000000000000000000000000000000000000000..0ba694af6c07d947d219b45a629bd32c60a0f5fe GIT binary patch literal 355 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQ*)Bra@SU# zmiz#bR~{$s2si{S(aY|Z}Vd7tb ouUmn-_&~Y>fYve?8dVq?X&Y!8wB+ut16ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LV0RR91EC2ui01N;O000Q30RR2_S4&EY6iQm%`}gnPJt+_{K)?V2 zLXLC){%HZw>fgVA|EvLsajzEu00000z;I3r005M<000000DxM(0001xBL)Bf002n2 z7XttQ00?3L0001hwORlG004kM3jhEBa{QA500000F=_z-0D#dw1ONa40Kn*<5&!^d q)dBzj0Du(t{=E|d00000005*o_wV1of1Gk4K#+pby?_4#0suR4=(v0U literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/7.png b/mvn-build/core/docs/reference/images/callouts/7.png new file mode 100644 index 0000000000000000000000000000000000000000..472e96f8ac36862c5645732f2fff19d06ee11a8e GIT binary patch literal 344 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQu%w%U~xZhnMEEs6JbBSw2NpOBzNqJ&XDuaWDvZiNlVtT4?VtQtBwt|VC zk)DNurKP@sg}#BILUK`NaY>~^Ze?*vX0l#tv0h4PL4IC|UUGi!#9fLzfW~Qojmu2W zODrh`nrCEbVQgk$XkwI@Y+{_8nv`N>YGIaQkz#0QY@Te9lBQ<)awbq0A4pdK&{_sV bqY6VKZ3AtCmfYR7Kp6&4S3j3^P66ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LV0RR91EC2ui01N;O000Q30RR2_*Gfu?5K>D0`}gnPJ0$=BKnMW< zK#p_&{s{p9pp-Xjy&wRHajz5r07~vb0B~*&005Mf001E59s~sM)&Kwy0|Nj60BW7{ zW(@%V00=Mu0HEcb6eaK0004kM2><}4dI12?suutNF-iddFy~#05a$R106^)V5C8}$ qoqIu&S`V$cAh@R~F=4@V4jxkzlaQrcFYWK{)(`o5XZnut z=nE4SU2g1ZW%;@@I$>_e3F8a=8WK~|CVXt1DqisQxtIX|`YW_n&?Nh#1gQ}d)$LrYTw(_{nVG)tp2V+#}WG*e^KRLdkoLz7g? qn(IA84Qgo42`r6v<+Hvch>@C7(8A5T-G@yGywn*$#_oy literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/9.gif b/mvn-build/core/docs/reference/images/callouts/9.gif new file mode 100644 index 0000000000000000000000000000000000000000..bc5c8125b03849986c2df04f65f7406235c700a6 GIT binary patch literal 968 zcmV;(12_CfNk%w1VGIBa0QUd@000010RaL60s{jB1Ox;H1qB8M1_uWR2nYxX2?+`c z3JVJh3=9kn4Gj(s4i66x5D*X%5fKs+5)%^>6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LV0RR91EC2ui01N;O000Q30RR2_*Gfu?5K>y*`}gnPJs|)9K!5-M zK#q6+{wV=~pp~_Ar2qiPajz8s0CT({z;I6s005Mf002;`6#xL%N&x_n0|Nj6d%PjQ zaIY5t00=Mu005!ayL0Di0RVtN2mk;85FkKcjS>I=F-iddAmm;MCI4~&0Kn*84gdgJ q{d>0t0Du(t{=JI<00062006}B_V3@nf0SZCKoEn`y?_4#0suSwcfTJ1 literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/images/callouts/9.png b/mvn-build/core/docs/reference/images/callouts/9.png new file mode 100644 index 0000000000000000000000000000000000000000..a0676d26cc2ff1de12c4ecdeefb44a0d71bc6bde GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQNRqa;^5&H%t0&v*|C|wdb9$wI zR@+N9#RIowg@Uqn&z-__Tzhhz!sG|vTxA7?=O|Y?u(d4T{!RM9c7chr6d%1?R=i16 z?@Ic{f32YJFJnVhX)qGzOMplv!L->5yAlT#}irms+fsQd*FoSE84k zpF44v;trs3T43Wc)AJHbN`dAXo0u6Hr<$gkq?lM38ycjV7+5A5Sr{ayr5c%-n;95g pF*H#D>f!_G3IJNmU}#ifXryhRZP1dtyA~+J;OXk;vd$@?2>@J{cB%jX literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/readme.txt b/mvn-build/core/docs/reference/readme.txt new file mode 100644 index 00000000..6280723d --- /dev/null +++ b/mvn-build/core/docs/reference/readme.txt @@ -0,0 +1,20 @@ +This project uses the 'DocBook XSL distribution' for HTML and PDF +generation of project reference documentation. + +This project's build.xml file contains targets to generate the +project reference documentation. + +To generate project documentation, execute one of the following +build targets: + +* doc-all - generate documentation in all supported formats +* doc-pdf - generate the PDF documentation +* doc-html - generate the HTML documentation +* doc-htmlsingle - generate single page HTML documentation +* doc-clean - clean any output directories for docs + +For generation to complete successfully, you must have first extracted +the .jar libraries contained in this archive: + - http://static.springframework.org/spring/files/docbook-reference-libs.zip +... to ${basedir}/docs/reference. If you have not yet done so, download this file +and unzip the contents of the archive into ${basedir}/docs/reference. \ No newline at end of file diff --git a/mvn-build/core/docs/reference/src/basic.xml b/mvn-build/core/docs/reference/src/basic.xml new file mode 100644 index 00000000..c6e0f552 --- /dev/null +++ b/mvn-build/core/docs/reference/src/basic.xml @@ -0,0 +1,385 @@ + + + Basic Operations + + + Search and Lookup Using AttributesMapper + + In this example we will use an AttributesMapper + to easily build a List of all common names of all person objects. + + + AttributesMapper that returns a single attribute + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public List getAllPersonNames() { + return ldapTemplate.search( + "", "(objectclass=person)", + new AttributesMapper() { + public Object mapFromAttributes(Attributes attrs) + throws NamingException { + return attrs.get("cn").get(); + } + }); + } +} + + + The inline implementation of AttributesMapper + just gets the desired attribute value from the + Attributes and returns it. Internally, + LdapTemplate iterates over all entries found, calling + the given AttributesMapper for each entry, and collects + the results in a list. The list is then returned by the + search method. + + Note that the AttributesMapper implementation + could easily be modified to return a full Person + object: + + + AttributesMapper that returns a Person object + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + private class PersonAttributesMapper implements AttributesMapper { + public Object mapFromAttributes(Attributes attrs) throws NamingException { + Person person = new Person(); + person.setFullName((String)attrs.get("cn").get()); + person.setLastName((String)attrs.get("sn").get()); + person.setDescription((String)attrs.get("description").get()); + return person; + } + } + + public List getAllPersons() { + return ldapTemplate.search("", "(objectclass=person)", new PersonAttributesMapper()); + } +} + + + If you have the distinguished name (dn) that + identifies an entry, you can retrieve the entry directly, without + searching for it. This is called a lookup in Java + LDAP. The following example shows how a lookup results in a Person + object: + + + A lookup resulting in a Person object + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public Person findPerson(String dn) { + return (Person) ldapTemplate.lookup(dn, new PersonAttributesMapper()); + } +} + + + This will look up the specified dn and pass the + found attributes to the supplied AttributesMapper, in + this case resulting in a Person object. + + + + Building Dynamic Filters + + We can build dynamic filters to use in searches, using the classes + from the org.springframework.ldap.filter + package. Let's say that we want the following filter: + (&(objectclass=person)(sn=?)), where we want the + ? to be replaced with the value of the parameter + lastName. This is how we do it using the filter support + classes: + + + Building a search filter dynamically + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public List getPersonNamesByLastName(String lastName) { + AndFilter filter = new AndFilter(); + filter.and(new EqualsFilter("objectclass", "person")); + filter.and(new EqualsFilter("sn", lastName)); + return ldapTemplate.search( + "", filter.encode(), + new AttributesMapper() { + public Object mapFromAttributes(Attributes attrs) + throws NamingException { + return attrs.get("cn").get(); + } + }); + } +} + + + To perform a wildcard search, it's possible to use the + WhitespaceWildcardsFilter: + + + Building a wildcard search filter + + AndFilter filter = new AndFilter(); +filter.and(new EqualsFilter("objectclass", "person")); +filter.and(new WhitespaceWildcardsFilter("cn", cn)); + + + + + In addition to simplifying building of complex search filters, + the Filter classes also provide proper escaping + of any unsafe characters. This prevents "ldap injection", + where a user might use such characters to inject unwanted operations + into your LDAP operations. + + + + + + Building Dynamic Distinguished Names + + The standard Name + interface represents a generic name, which is basically an ordered + sequence of components. The Name interface also + provides operations on that sequence; e.g., add or + remove. LdapTemplate provides an implementation of the + Name interface: DistinguishedName. + Using this class will greatly simplify building distinguished names, + especially considering the sometimes complex rules regarding escapings and + encodings. As with the Filter classes this helps preventing + potentially malicious data being injected into your LDAP operations. + + + The following example illustrates how + DistinguishedName can be used to dynamically construct + a distinguished name: + + + Building a distinguished name dynamically + + package com.example.dao; + +import org.springframework.ldap.core.support.DistinguishedName; +import javax.naming.Name; + +public class PersonDaoImpl implements PersonDao { + public static final String BASE_DN = "dc=example,dc=com"; + ... + protected Name buildDn(Person p) { + DistinguishedName dn = new DistinguishedName(BASE_DN); + dn.add("c", p.getCountry()); + dn.add("ou", p.getCompany()); + dn.add("cn", p.getFullname()); + return dn; + } +} + + + Assuming that a Person has the following attributes: + + + + + + country + + Sweden + + + + company + + Some Company + + + + fullname + + Some Person + + + + + + The code above would then result in the following distinguished + name: + + cn=Some Person, ou=Some Company, c=Sweden, dc=example, dc=com + + In Java 5, there is an implementation of the Name interface: LdapName. + If you are in the Java 5 world, you might as well use + LdapName. However, you may still use + DistinguishedName if you so wish. + + + + Binding and Unbinding + + + Binding Data + + Inserting data in Java LDAP is called binding. In order to do + that, a distinguished name that uniquely identifies the new entry is + required. The following example shows how data is bound using + LdapTemplate: + + + Binding data using Attributes + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public void create(Person p) { + Name dn = buildDn(p); + ldapTemplate.bind(dn, null, buildAttributes(p)); + } + + private Attributes buildAttributes(Person p) { + Attributes attrs = new BasicAttributes(); + BasicAttribute ocattr = new BasicAttribute("objectclass"); + ocattr.add("top"); + ocattr.add("person"); + attrs.put(ocattr); + attrs.put("cn", "Some Person"); + attrs.put("sn", "Person"); + return attrs; + } +} + + + The Attributes building is--while dull and verbose--sufficient for + many purposes. It is, however, possible to simplify the binding + operation further, which will be described in . + + + + Unbinding Data + + Removing data in Java LDAP is called unbinding. A distinguished + name (dn) is required to identify the entry, just as in the binding + operation. The following example shows how data is unbound using + LdapTemplate: + + + Unbinding data + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public void delete(Person p) { + Name dn = buildDn(p); + ldapTemplate.unbind(dn); + } +} + + + + + + Modifying + + In Java LDAP, data can be modified in two ways: either using + rebind or + modifyAttributes. + + + Modifying using <literal>rebind</literal> + + A rebind is a very crude way to modify data. + It's basically an unbind followed by a + bind. It looks like this: + + + Modifying using rebind + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public void update(Person p) { + Name dn = buildDn(p); + ldapTemplate.rebind(dn, null, buildAttributes(p)); + } +} + + + + + Modifying using <literal>modifyAttributes</literal> + + If only the modified attributes should be replaced, there is a + method called modifyAttributes that takes an array of + modifications: + + + Modifying using modifyAttributes + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public void updateDescription(Person p) { + Name dn = buildDn(p); + Attribute attr = new BasicAttribute("description", p.getDescription()) + ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr); + ldapTemplate.modifyAttributes(dn, new ModificationItem[] {item}); + } +} + + + Building Attributes and + ModificationItem arrays is a lot of work, but as you + will see in , the update operations + can be simplified. + + + + + Sample applications + + It is recommended that you review the Spring LDAP sample + applications included in the release distribution for best-practice + illustrations of the features of this library. A description of each + sample is provided below: + + + + spring-ldap-person - the sample demonstrating most + features. + + + + spring-ldap-article - the sample application that was written + to accompany a java.net + article about Spring LDAP. + + + + \ No newline at end of file diff --git a/mvn-build/core/docs/reference/src/configuration.xml b/mvn-build/core/docs/reference/src/configuration.xml new file mode 100644 index 00000000..9555a24f --- /dev/null +++ b/mvn-build/core/docs/reference/src/configuration.xml @@ -0,0 +1,259 @@ + + + Configuration + + + ContextSource Configuration + + There are several properties in AbstractContextSource + (superclass of DirContextSource and LdapContextSource) + that can be used to modify its behaviour. + + + LDAP Server URLs + + The URL of the LDAP server is specified using the url property. + The URL should be in the format ldap://myserver.example.com:389. + For SSL access, use the ldaps protocol and the appropriate port, e.g. + ldaps://myserver.example.com:636 + It is possible to configure multiple alternate LDAP servers using the + urls property. In this case, supply all server urls in a String + array to the urls property. + + + + Base LDAP path + + It is possible to specify the root context for all LDAP operations using the + base property of AbstractContextSource. + When a value has been specified to this property, all Distinguished Names supplied to and received from LDAP operations + will be relative to the LDAP path supplied. This can significantly simplify working against the LDAP + tree; however there are several occations when you will need to have access to the base path. + For more information on this, please refer to + + + + Authentication + + Authenticated contexts are created for both read-only and + read-write operations by default. You specify + userDn and password of the LDAP + user to be used for authentication on the + ContextSource. + + + The userDn needs to be the full + Distinguished Name (DN) of the user. + + + Some LDAP server setups allow anonymous read-only access. If you + want to use anonymous Contexts for read-only operations, set the + anonymousReadOnly property to + true. + + + Custom Authentication Using Acegi + + While the user name (i.e. user DN) and password used for + creating an authenticated Context are static by + default - the ones set on the ContextSource on + startup will be used throughout the lifetime of the + ContextSource - there are however several cases in + which this is not the desired behaviour. A common scenario is that the + principal and credentials of the current user should be used when + executing LDAP operations for that user. The default behaviour can be + modified by supplying a custom AuthenticationSource + implementation to the ContextSource on startup, + instead of explicitly specifying the userDn and + password. The + AuthenticationSource will be queried by the + ContextSource for principal and credentials each + time an authenticated Context is to be + created. + + To use the authentication information of the currently logged in + user using Acegi + Security, use the + AcegiAuthenticationSource: + + + The Spring bean definition for an + AcegiAuthenticationSource + + <beans> + ... + <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> + <property name="url" value="ldap://localhost:389" /> + <property name="base" value="dc=example,dc=com" /> + <property name="authenticationSource" ref="acegiAuthenticationSource" /> + </bean> + + <bean id="acegiAuthenticationSource" + class="org.springframework.ldap.authentication.AcegiAuthenticationSource" /> + ... +</beans> + + + + We don't specify any userDn or + password to our ContextSource + when using an AuthenticationSource - these + properties are needed only when the default behaviour is + used. + + + + When using the AcegiAuthenticationSource + you need to use Acegi's + LdapAuthenticationProvider to authenticate the + users against LDAP. + + + + + Default Authentication + + When using AcegiAuthenticationSource, + authenticated contexts will only be possible to create once the user + is logged in using Acegi. To use default authentication information + when no user is logged in, use the + DefaultValuesAuthenticationSourceDecorator: + + + Configuring a + DefaultValuesAuthenticationSourceDecorator + + <beans> + ... + <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> + <property name="url" value="ldap://localhost:389" /> + <property name="base" value="dc=example,dc=com" /> + <property name="authenticationSource" ref="authenticationSource" /> + </bean> + + <bean id="authenticationSource" + class="org.springframework.ldap.authentication.DefaultValuesAuthenticationSourceDecorator"> + <property name="target" ref="acegiAuthenticationSource" /> + <property name="defaultUser" value="cn=myDefaultUser" /> + <property name="defaultPassword" value="pass" /> + </bean> + + <bean id="acegiAuthenticationSource" + class="org.springframework.ldap.authentication.AcegiAuthenticationSource" /> + ... +</beans> + + + + + + Pooling + + LDAP connection pooling can be turned on/off using the + pooled flag. Default is true. The + configuration of LDAP connection pooling is managed using + System properties, so this needs to be handled + manually. Details of pooling configuration can be found here. + + + + Advanced ContextSource Configuration + + Alternate ContextFactory + + It is possible to configure the ContextFactory that the + ContextSource is to use when creating Contexts using the + contextFactory property. The default value is + com.sun.jndi.ldap.LdapCtxFactory. + + + Custom DirObjectFactory + + As described in , a DirObjectFactory + can be used to translate the Attributes of found Contexts + to a more useful DirContext implementation. This can be + configured using the dirObjectFactory property. You can use + this property if you have your own, custom DirObjectFactory implementation. + The default value is DefaultDirObjectFactory. + + + Custom DirContext Environment Properties + + In some cases the user might want to specify additional environment setup properties + in addition to the ones directly configurable from AbstractContextSource. + Such properties should be set in a Map and supplied to + the baseEnvironmentProperties property. + + + + + LdapTemplate Configuration + + + Ignoring PartialResultExceptions + + Some Active Directory (AD) servers are unable to automatically following + referrals, which often leads to a PartialResultException being + thrown in searches. You can specify that PartialResultException + is to be ignored by setting the ignorePartialResultException + property to true. + This causes all referrals to be ignored, and no notice will be given that + a PartialResultException has been encountered. + There is currently no way of manually following referrals using LdapTemplate. + + + + Obtaining a reference to the base LDAP path + As described above, a base LDAP path may be supplied to the ContextSource, + specifying the root in the LDAP tree to which all operations will be relative. This means that + you will only be working with relative distinguished names throughout your system, which is + typically rather handy. There are however some cases in which you will need to have access + to the base path in order to be able to construct full DNs, relative to the actual root of the LDAP tree. + One example would be when working with LDAP groups (e.g. groupOfNames objectclass), + in which case each group member attribute value will need to be the full DN of the referenced member. + For that reason, Spring LDAP has a mechanism by which any Spring controlled bean may be supplied + the base path on startup. For beans to be notified of the base path, two things need to be in place: + First of all, the bean that wants the base path reference needs to implement the + BaseLdapPathAware interface. Secondly, a BaseLdapPathBeanPostProcessor + needs to be defined in the application context + + Implementing <literal>BaseLdapPathAware</literal> + + package com.example.service; + +public class PersonService implements PersonService, BaseLdapPathAware { + ... + private DistinguishedName basePath; + + public void setBaseLdapPath(DistinguishedName basePath) { + this.basePath = basePath; + } + ... + private DistinguishedName getFullPersonDn(Person person) { + return new DistinguishedName(basePath).append(person.getDn()); + } + ... +} + + + Specifying a <literal>BaseLdapPathBeanPostProcessor</literal> in your <literal>ApplicationContext</literal> + + <beans> + ... + <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> + <property name="url" value="ldap://localhost:389" /> + <property name="base" value="dc=example,dc=com" /> + <property name="acegiAuthenticationSource" ref="authenticationSource" /> + </bean> + ... + <bean class="org.springframework.ldap.core.support.BaseLdapPathBeanPostProcessor" /> +</beans> + + + The default behaviour of the BaseLdapPathBeanPostProcessor is to use the base path of the single + defined BaseLdapPathSource (AbstractContextSource )in the ApplicationContext. + If more than one BaseLdapPathSource is defined, you will need to specify which one to use with the + baseLdapPathSourceName property. + + \ No newline at end of file diff --git a/mvn-build/core/docs/reference/src/contextprocessor.xml b/mvn-build/core/docs/reference/src/contextprocessor.xml new file mode 100644 index 00000000..9f8bf51c --- /dev/null +++ b/mvn-build/core/docs/reference/src/contextprocessor.xml @@ -0,0 +1,200 @@ + + + Processing the DirContext + + + Custom DirContext Pre/Postprocessing + + In some situations, one would like to perform operations on the + DirContext before and after the search operation. The + interface that is used for this is called + DirContextProcessor: + + + public interface DirContextProcessor { + public void preProcess(DirContext ctx) throws NamingException; + public void postProcess(DirContext ctx) throws NamingException; +} + + + The LdapTemplate class has a search method that + takes a DirContextProcessor: + + + public void search(SearchExecutor se, NameClassPairCallbackHandler handler, + DirContextProcessor processor) throws DataAccessException; + + + Before the search operation, the preProcess + method is called on the given DirContextProcessor + instance. After the search has been executed and the resulting + NamingEnumeration has been processed, the + postProcess method is called. This enables a user to + perform operations on the DirContext to be used in the + search, and to check the DirContext when the search has + been performed. This can be very useful for example when handling request + and response controls. + + There are also a few convenience methods for those that don't need a + custom SearchExecutor: + + + public void search(Name base, String filter, + SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor) + +public void search(String base, String filter, + SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor) + +public void search(Name base, String filter, + SearchControls controls, AttributesMapper mapper, DirContextProcessor processor) + +public void search(String base, String filter, + SearchControls controls, AttributesMapper mapper, DirContextProcessor processor) + +public void search(Name base, String filter, + SearchControls controls, ContextMapper mapper, DirContextProcessor processor) + +public void search(String base, String filter, + SearchControls controls, ContextMapper mapper, DirContextProcessor processor) + + + + + Implementing a Request Control DirContextProcessor + + The LDAPv3 protocol uses Controls to send and receive additional + data to affect the behavior of predefined operations. In order to simplify + the implementation of a request control + DirContextProcessor, Spring LDAP provides the base + class AbstractRequestControlDirContextProcessor. This + class handles the retrieval of the current request controls from the + LdapContext, calls a template method for creating a + request control, and adds it to the LdapContext. All + you have to do in the subclass is to implement the template method + createRequestControl, and of course the + postProcess method for performing whatever you need to + do after the search. + + + public abstract class AbstractRequestControlDirContextProcessor implements + DirContextProcessor { + + public void preProcess(DirContext ctx) throws NamingException { + ... + } + + public abstract Control createRequestControl(); +} + + A typical DirContextProcessor will be similar to the following: + + + + A request control DirContextProcessor implementation + + package com.example.control; + +public class MyCoolRequestControl extends AbstractRequestControlDirContextProcessor { + private static final boolean CRITICAL_CONTROL = true; + private MyCoolCookie cookie; + ... + public MyCoolCookie getCookie() { + return cookie; + } + + public Control createRequestControl() { + return new SomeCoolControl(cookie.getCookie(), CRITICAL_CONTROL); + } + + public void postProcess(DirContext ctx) throws NamingException { + LdapContext ldapContext = (LdapContext) ctx; + Control[] responseControls = ldapContext.getResponseControls(); + + for (int i = 0; i < responseControls.length; i++) { + if (responseControls[i] instanceof SomeCoolResponseControl) { + SomeCoolResponseControl control = (SomeCoolResponseControl) responseControls[i]; + this.cookie = new MyCoolCookie(control.getCookie()); + } + } + } +} + + + + Make sure you use LdapContextSource when you + use Controls. The Control + interface is specific for LDAPv3 and requires that + LdapContext is used instead of + DirContext. If an + AbstractRequestControlDirContextProcessor subclass is + called with an argument that is not an LdapContext, + it will throw an IllegalArgumentException. + + + + + Paged Search Results + + Some searches may return large numbers of results. When there is no + easy way to filter out a smaller amount, it would be convenient to have + the server return only a certain number of results each time it is called. + This is known as paged search results. Each "page" of + the result could then be displayed at the time, with links to the next and + previous page. Without this functionality, the client must either manually + limit the search result into pages, or retrieve the whole result and then + chop it into pages of suitable size. The former would be rather + complicated, and the latter would be consuming unnecessary amounts of + memory. + + Some LDAP servers have support for the + PagedResultsControl, which requests that the results of + a search operation are returned by the LDAP server in pages of a specified + size. The user controls the rate at which the pages are returned, simply + by the rate at which the searches are called. However, the user must keep + track of a cookie between the calls. The server uses + this cookie to keep track of where it left off the previous time it was + called with a paged results request. + + Spring LDAP provides support for paged results by leveraging the + concept for pre- and postprocessing of an LdapContext that was discussed + in the previous sections. It does so by providing two classes: + PagedResultsRequestControl and + PagedResultsCookie. The + PagedResultsRequestControl class creates a + PagedResultsControl with the requested page size and + adds it to the LdapContext. After the search, it gets + the PagedResultsResponseControl and retrieves two + pieces of information from it: the estimated total result size and a + cookie. This cookie is a byte array containing information that the server + needs the next time it is called with a + PagedResultsControl. In order to make it easy to store + this cookie between searches, Spring LDAP provides the wrapper class + PagedResultsCookie. + + Below is an example of how the paged search results functionality may + be used: + + + Paged results using <literal>PagedResultsRequestControl</literal> + + public PagedResult getAllPersons(PagedResultsCookie cookie) { + PagedResultsRequestControl control = new PagedResultsRequestControl(PAGE_SIZE, cookie); + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + List persons = ldapTemplate.search("", "objectclass=person", searchControls, control); + + return new PagedResult(persons, control.getCookie()); + } + + + In the first call to this method, null will be supplied as + the cookie parameter. On subsequent calls the client will need to supply the cookie from + the last search (returned wrapped in the PagedResult) each time the + method is called. When the actual cookie is null (i.e. + pagedResult.getCookie().getCookie() returns null), + the last batch has been returned from the search. + + + \ No newline at end of file diff --git a/mvn-build/core/docs/reference/src/dirobjectfactory.xml b/mvn-build/core/docs/reference/src/dirobjectfactory.xml new file mode 100644 index 00000000..0892ccc9 --- /dev/null +++ b/mvn-build/core/docs/reference/src/dirobjectfactory.xml @@ -0,0 +1,341 @@ + + + Simpler Attribute Access and Manipulation with DirContextAdapter + + + Introduction + + A little-known--and probably underestimated--feature of the Java + LDAP API is the ability to register a DirObjectFactory + to automatically create objects from found contexts. One of the reasons + why it is seldom used is that you will need an implementation of + DirObjectFactory that creates instances of a meaningful + implementation of DirContext. The Spring LDAP library + provides the missing pieces: a default implementation of + DirContext called DirContextAdapter, + and a corresponding implementation of DirObjectFactory + called DefaultDirObjectFactory. Used together with + DefaultDirObjectFactory, the + DirContextAdapter can be a very powerful tool. + + + + Search and Lookup Using ContextMapper + + The DefaultDirObjectFactory is registered with + the ContextSource by default, which means that whenever + a context is found in the LDAP tree, its Attributes and + Distinguished Name (DN) will be used to construct a + DirContextAdapter. This enables us to use a + ContextMapper instead of an + AttributesMapper to transform found values: + + + Searching using a ContextMapper + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + private static class PersonContextMapper implements ContextMapper { + public Object mapFromContext(Object ctx) { + DirContextAdapter context = (DirContextAdapter)ctx; + Person p = new Person(); + p.setFullName(context.getStringAttribute("cn")); + p.setLastName(context.getStringAttribute("sn")); + p.setDescription(context.getStringAttribute("description")); + return p; + } + } + + public Person findByPrimaryKey( + String name, String company, String country) { + Name dn = buildDn(name, company, country); + return ldapTemplate.lookup(dn, new PersonContextMapper()); + } +} + + + The above code shows that it is possible to retrieve the attributes + directly by name, without having to go through the + Attributes and BasicAttribute + classes. This is particularly useful when working with multi-value attributes. Extracting values from + multi-value attributes normally requires looping through a NamingEnumeration of + attribute values returned from the Attributes implementation. The + DirContextAdapter can do this for you, using the getStringAttributes() + or getObjectAttributes() methods: + + Getting multi-value attribute values using <literal>getStringAttributes()</literal> + + private static class PersonContextMapper implements ContextMapper { + public Object mapFromContext(Object ctx) { + DirContextAdapter context = (DirContextAdapter)ctx; + Person p = new Person(); + p.setFullName(context.getStringAttribute("cn")); + p.setLastName(context.getStringAttribute("sn")); + p.setDescription(context.getStringAttribute("description")); + // The roleNames property of Person is an String array + p.setRoleNames(context.getStringAttributes("roleNames")); + return p; + } +} + + + + + The AbstractContextMapper + Spring LDAP provides an abstract base implementation of ContextMapper, + AbstractContextMapper. This automatically takes care of the casting of the supplied + Object parameter to DirContexOperations. + The PersonContextMapper above can thus be re-written as follows: + + + Using an AbstractContextMapper + + + private static class PersonContextMapper extends AbstractContextMapper { + public Object doMapFromContext(DirContextOperations ctx) { + Person p = new Person(); + p.setFullName(context.getStringAttribute("cn")); + p.setLastName(context.getStringAttribute("sn")); + p.setDescription(context.getStringAttribute("description")); + return p; + } + } + + + + + + + Binding and Modifying Using DirContextAdapter + + While very useful when extracting attribute values, DirContextAdapter is even more + powerful for hiding attribute details when binding and modifying data. + + + Binding + + This is an example of an improved implementation of the create DAO + method. Compare it with the previous implementation in . + + + Binding using <literal>DirContextAdapter</literal> + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + public void create(Person p) { + Name dn = buildDn(p); + DirContextAdapter context = new DirContextAdapter(dn); + + context.setAttributeValues("objectclass", new String[] {"top", "person"}); + context.setAttributeValue("cn", p.getFullname()); + context.setAttributeValue("sn", p.getLastname()); + context.setAttributeValue("description", p.getDescription()); + + ldapTemplate.bind(dn, context, null); + } +} + + + Note that we use the DirContextAdapter instance + as the second parameter to bind, which should be a Context. + The third parameter is null, since we're not using any + Attributes. + Also note the use of the setAttributeValues() method when setting the + objectclass attribute values. The objectclass attribute is + multi-value, and similar to the troubles of extracting muti-value attribute data, building multi-value + attributes is tedious and verbose work. Using the setAttributeValues() mehtod you can + have DirContextAdapter handle that work for you. + + + + Modifying + + The code for a rebind would be pretty much + identical to , except + that the method called would be rebind. As we saw in + a more correct approach would be to + build a ModificationItem array containing the actual + modifications you want to do. This would require you to determine the actual + modifications compared to the data present in the LDAP tree. Again, this + is something that DirContextAdapter can help you with; the + DirContextAdapter has the ability to keep track of + its modified attributes. The following example takes advantage of this + feature: + + + Modifying using <literal>DirContextAdapter</literal> + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + public void update(Person p) { + Name dn = buildDn(p); + DirContextOperations context = ldapTemplate.lookupContext(dn); + + context.setAttributeValues("objectclass", new String[] {"top", "person"}); + context.setAttributeValue("cn", p.getFullname()); + context.setAttributeValue("sn", p.getLastname()); + context.setAttributeValue("description", p.getDescription()); + + ldapTemplate.modifyAttributes(context); + } +} + + When no mapper is passed to a ldapTemplate.lookup() operation, + the result will be a DirContextAdapter instance. + While the lookup method returns an Object, the convenience + method lookupContext method automatically casts the return value to + a DirContextOperations (the interface that DirContextAdapter implements. + The observant reader will see that we have duplicated code in the + create and update methods. This + code maps from a domain object to a context. It can be extracted to a + separate method: + + + Binding and modifying using DirContextAdapter + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + + ... + public void create(Person p) { + Name dn = buildDn(p); + DirContextAdapter context = new DirContextAdapter(dn); + mapToContext(p, context); + ldapTemplate.bind(dn, context, null); + } + + public void update(Person p) { + Name dn = buildDn(p); + DirContextOperations context = ldapTemplate.lookupContext(dn); + mapToContext(person, context); + ldapTemplate.modifyAttributes(context); + } + + protected void mapToContext (Person p, DirContextOperations context) { + context.setAttributeValues("objectclass", new String[] {"top", "person"}); + context.setAttributeValue("cn", p.getFullName()); + context.setAttributeValue("sn", p.getLastName()); + context.setAttributeValue("description", p.getDescription()); + } +} + + + + + + A Complete PersonDao Class + + To illustrate the power of Spring LDAP, here is a complete Person + DAO implementation for LDAP in just 68 lines: + + + A complete PersonDao class + + package com.example.dao; + +import java.util.List; + +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.ContextMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.support.DistinguishedName; +import org.springframework.ldap.filter.EqualsFilter; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public void create(Person person) { + DirContextAdapter context = new DirContextAdapter(); + mapToContext(person, context); + ldapTemplate.bind(buildDn(person), context, null); + } + + public void update(Person person) { + Name dn = buildDn(person); + DirContextOperations context = ldapTemplate.lookupContext(dn); + mapToContext(person, context); + ldapTemplate.modifyAttributes(context); + } + + public void delete(Person person) { + ldapTemplate.unbind(buildDn(person)); + } + + public Person findByPrimaryKey(String name, String company, String country) { + Name dn = buildDn(name, company, country); + return (Person) ldapTemplate.lookup(dn, getContextMapper()); + } + + public List findAll() { + EqualsFilter filter = new EqualsFilter("objectclass", "person"); + return ldapTemplate.search(DistinguishedName.EMPTY_PATH, filter.encode(), getContextMapper()); + } + + protected ContextMapper getContextMapper() { + return new PersonContextMapper(); + } + + protected Name buildDn(Person person) { + return buildDn(person.getFullname(), person.getCompany(), person.getCountry()); + } + + protected Name buildDn(String fullname, String company, String country) { + DistinguishedName dn = new DistinguishedName(); + dn.add("c", country); + dn.add("ou", company); + dn.add("cn", fullname); + return dn; + } + + protected void mapToContext(Person person, DirContextOperations context) { + context.setAttributeValues("objectclass", new String[] {"top", "person"}); + context.setAttributeValue("cn", person.getFullName()); + context.setAttributeValue("sn", person.getLastName()); + context.setAttributeValue("description", person.getDescription()); + } + + private static class PersonContextMapper extends AbstractContextMapper { + public Object doMapFromContext(DirContextOperations context) { + Person person = new Person(); + person.setFullName(context.getStringAttribute("cn")); + person.setLastName(context.getStringAttribute("sn")); + person.setDescription(context.getStringAttribute("description")); + return person; + } + } +} + + + + In several cases the Distinguished Name (DN) of an object is + constructed using properties of the object. E.g. in the above example, + the country, company and full name of the Person are + used in the DN, which means that updating any of these properties will + actually require moving the entry in the LDAP tree using the + rename() operation in addition to updating the + Attribute values. Since this is highly implementation + specific this is something you'll need to keep track of yourself - + either by disallowing the user to change these properties or performing + the rename() operation in your + update() method if needed. + + + \ No newline at end of file diff --git a/mvn-build/core/docs/reference/src/executors.xml b/mvn-build/core/docs/reference/src/executors.xml new file mode 100644 index 00000000..cadc78e8 --- /dev/null +++ b/mvn-build/core/docs/reference/src/executors.xml @@ -0,0 +1,148 @@ + + + Adding Missing Overloaded API Methods + + + Implementing Custom Search Methods + + While LdapTemplate contains several overloaded + versions of the most common operations in DirContext, + we have not provided an alternative for each and every method signature, + mostly because there are so many of them. We have, however, provided a + means to call whichever DirContext method you want + and still get the benefits that LdapTemplate provides. + + Let's say that you want to call the following DirContext + method: + + NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, SearchControls ctls) + + There is no corresponding overloaded method in LdapTemplate. The way to solve + this is to use a custom SearchExecutor implementation: + + + public interface SearchExecutor { + public NamingEnumeration executeSearch(DirContext ctx) throws NamingException; +} + + + In your custom executor, you have access to a DirContext + object, which you use to call the method you want. You then provide a handler + that is responsible for mapping attributes and collecting the results. You can + for example use one of the available implementations of + CollectingNameClassPairCallbackHandler, which will collect + the mapped results in an internal list. In order to + actually execute the search, you call the search + method in LdapTemplate that takes an executor and a handler as arguments. Finally, + you return whatever your handler has collected. + + + A custom search method using SearchExecutor and + AttributesMapper + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + public List search(final Name base, final String filter, final String[] params, + final SearchControls ctls) { + SearchExecutor executor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) { + return ctx.search(base, filter, params, ctls); + } + }; + + CollectingNameClassPairCallbackHandler handler = + new AttributesMapperCallbackHandler(new PersonAttributesMapper()); + + ldapTemplate.search(executor, handler); + return handler.getList(); + } +} + + + If you prefer the ContextMapper to the + AttributesMapper, this is what it would look + like: + + + A custom search method using SearchExecutor and + ContextMapper + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + public List search(final Name base, final String filter, final String[] params, + final SearchControls ctls) { + SearchExecutor executor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) { + return ctx.search(base, filter, params, ctls); + } + }; + + CollectingNameClassPairCallbackHandler handler = + new ContextMapperCallbackHandler(new PersonContextMapper()); + + ldapTemplate.search(executor, handler); + return handler.getList(); + } +} + + + + When using the + ContextMapperCallbackHandler you must + make sure that you have called + setReturningObjFlag(true) on your + SearchControls instance. + + + + + Implementing Other Custom Context Methods + + In the same manner as for custom search methods, + you can actually execute any method in DirContext by + using a ContextExecutor. + + + public interface ContextExecutor { + public Object executeWithContext(DirContext ctx) throws NamingException; +} + + When implementing a custom ContextExecutor, you + can choose between using the executeReadOnly() or the + executeReadWrite() method. Let's say that we want to + call this method: + + + Object lookupLink(Name name) + + It's available in DirContext, but there is no + matching method in LdapTemplate. It's a lookup method, + so it should be read-only. We can implement it like this: + + + A custom DirContext method using ContextExecutor + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + public Object lookupLink(final Name name) { + ContextExecutor executor = new ContextExecutor() { + public Object executeWithContext(DirContext ctx) { + return ctx.lookupLink(name); + } + }; + + return ldapTemplate.executeReadOnly(executor); + } +} + + In the same manner you can execute a read-write operation using + the executeReadWrite() method. + + + \ No newline at end of file diff --git a/mvn-build/core/docs/reference/src/images/NamingException.png b/mvn-build/core/docs/reference/src/images/NamingException.png new file mode 100644 index 0000000000000000000000000000000000000000..8042b84e0cb8de38b32f00ea8e84810ba8ddbcf7 GIT binary patch literal 64786 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 l&ocQzY?HljSpcAP` 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

bT!>#wxmfe0(bKhNh;x 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 dUT5C;E5h&9dQ)R + + + + + + + + + + +]> + + + + + + Spring LDAP + Reference Documentation + Version 1.2.1 + December 2007 + + + Mattias + Arthursson + + + Ulrik + Sandberg + + + Eric + Dalquist + + + + + + Copies of this document may be made for your own use and + for distribution to others, provided that you do not + charge any fee for such copies and further provided that + each copy contains this Copyright Notice, whether + distributed in print or electronically. + + + + + + + &preface; + &overview; + &basic; + &dirobjectfactory; + &transactions; + &executors; + &contextprocessor; + &simple; + &configuration; + &pooling; + + \ No newline at end of file diff --git a/mvn-build/core/docs/reference/src/overview.xml b/mvn-build/core/docs/reference/src/overview.xml new file mode 100644 index 00000000..dbd53f14 --- /dev/null +++ b/mvn-build/core/docs/reference/src/overview.xml @@ -0,0 +1,417 @@ + + + Introduction + + + Overview + + Spring-LDAP (http://www.springframework.org/ldap) + is a library for simpler LDAP programming in Java, built on the same + principles as the JdbcTemplate + in Spring JDBC. It completely eliminates the need to worry about creating + and closing LdapContext and looping through + NamingEnumeration. It also provides a more + comprehensive unchecked Exception hierarchy, built on Spring's + DataAccessException. As a bonus, it also contains + classes for dynamically building LDAP filters and DNs (Distinguished + Names), LDAP attribute management, and client-side LDAP transaction management. + + Consider, for example, a method that should search some storage for + all persons and return their names in a list. Using JDBC, we would create + a connection and execute a query + using a statement. We would then loop over the + result set and retrieve the + column we want, adding it to a list. In contrast, + using Java LDAP, we would create a context and + perform a search using a search + filter. We would then loop over the resulting naming + enumeration and retrieve the attribute we + want, adding it to a list. + + The traditional way of implementing this person name search method + in Java LDAP looks like this, where the code marked as bold actually + performs tasks related to the business purpose of the method: + + + package com.example.dao; + +public class TraditionalPersonDaoImpl implements PersonDao { + public List getAllPersonNames() { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldap://localhost:389/dc=example,dc=com"); + + DirContext ctx; + try { + ctx = new InitialDirContext(env); + } catch (NamingException e) { + throw new RuntimeException(e); + } + + LinkedList list = new LinkedList(); + NamingEnumeration results = null; + try { + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + results = ctx.search("", "(objectclass=person)", controls); + + while (results.hasMore()) { + SearchResult searchResult = (SearchResult) results.next(); + Attributes attributes = searchResult.getAttributes(); + Attribute attr = attributes.get("cn"); + String cn = (String) attr.get(); + list.add(cn); + } + } catch (NameNotFoundException e) { + // The base context was not found. + // Just clean up and exit. + } catch (NamingException e) { + throw new RuntimeException(e); + } finally { + if (results != null) { + try { + results.close(); + } catch (Exception e) { + // Never mind this. + } + } + if (ctx != null) { + try { + ctx.close(); + } catch (Exception e) { + // Never mind this. + } + } + } + return list; + } +} + + + By using the Spring LDAP classes AttributesMapper + and LdapTemplate, we get the exact same functionality + with the following code: + + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public List getAllPersonNames() { + return ldapTemplate.search( + "", "(objectclass=person)", + new AttributesMapper() { + public Object mapFromAttributes(Attributes attrs) + throws NamingException { + return attrs.get("cn").get(); + } + }); + } +} + + + The amount of boiler-plate code is significantly less than in the + traditional example. The LdapTemplate version of the + search method performs the search, maps the attributes to a string using + the given AttributesMapper, collects the strings in an + internal list, and finally returns the list. + + Note that the PersonDaoImpl code simply assumes + that it has an LdapTemplate instance, rather than + looking one up somewhere. It provides a set method for this purpose. There + is nothing Spring-specific about this "Inversion of Control". Anyone that + can create an instance of PersonDaoImpl can also set + the LdapTemplate on it. However, Spring provides a very + flexible and easy way of achieving + this. The Spring container can be told to wire up an instance of + LdapTemplate with its required dependencies and inject + it into the PersonDao instance. This wiring can be + defined in various ways, but the most common is through XML: + + + <beans> + <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> + <property name="url" value="ldap://localhost:389" /> + <property name="base" value="dc=example,dc=com" /> + <property name="userDn" value="cn=Manager" /> + <property name="password" value="secret" /> + </bean> + + <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate"> + <constructor-arg ref="contextSource" /> + </bean> + + <bean id="personDao" class="com.example.dao.PersonDaoImpl"> + <property name="ldapTemplate" ref="ldapTemplate" /> + </bean> +</beans> + + + + + Packaging overview + + At a minimum, to use Spring LDAP you need: + + + + spring-ldap (the Spring LDAP library) + + + + spring-core (miscellaneous utility classes used internally by + the framework) + + + + spring-beans (contains interfaces and classes for manipulating + Java beans) + + + + commons-logging (a simple logging facade, used + internally) + + + + commons-lang (misc utilities, used internally) + + + + + In addition to the required dependencies the following optional dependencies + are required for certain functionality: + + + + acegi-security (For Acegi security integration using AcegiAuthenticationSource) + + + spring-context (If your application is wired up using the Spring Application Context - + adds the ability for application objects to obtain resources using a consistent API. Definitely needed if you are + planning on using the BaseLdapPathBeanPostProcessor.) + + + spring-dao (If you are planning to use the client side compensating transaction support) + + + spring-jdbc (If you are planning to use the client side compensating transaction support) + + + ldapbp (Sun LDAP Booster Pack - if you will use the LDAP v3 Server controls integration) + + + commons-pool (If you are planning to use the pooling functionality) + + + + + + + Package structure + + This section provides an overview of the logical package structure + of the Spring LDAP codebase. The dependencies for each package are clearly + noted. A package dependency noted as (optional) means + that the dependency is needed to compile the package but is optionally + needed at runtime (depending on your use of the package). For example, use + of Spring LDAP together with Acegi Security entails use of the + org.acegisecurity package. + +

+ Spring LDAP package structure + + + + + + +
+ + + org.springframework.transaction.compensating + + The transaction.compensating package contains + the generic compensating transaction support. This is not LDAP-specific + or JNDI-specific in any way. + + + + Dependencies: commons-logging + + + + + + org.springframework.ldap + + The ldap package contains the exceptions of + the library. These exceptions form an unchecked hierarchy that mirrors + the NamingException hierarchy. + + + + Dependencies: spring-core + + + + + + org.springframework.ldap.core + + The ldap.core package contains the central + abstractions of the library. These abstractions include + AuthenticationSource, ContextSource, DirContextProcessor, and + NameClassPairCallbackHandler. This package also contains the central + class LdapTemplate, plus various mappers and executors. + + + + Dependencies: ldap, ldap.support, spring-beans, + commons-lang, commons-logging + + + + + + org.springframework.ldap.core.support + + The ldap.core.support package contains + supporting implementations of some of the core interfaces. + + + + Dependencies: ldap.core, ldap.support, spring-core, + spring-beans, spring-context (optional), commons-lang, commons-logging + + + + + + org.springframework.ldap.support + + The ldap.support package contains supporting + utilities, like the exception translation mechanism. + + + + Dependencies: ldap, spring-core, commons-logging + + + + + + org.springframework.ldap.authentication + + The ldap.authentication package contains an + implementation of the AuthenticationSource interface that can be used + with Acegi Security, + as well as related helper classes. + + + + Dependencies: ldap.core, acegi-security (optional), + spring-beans, commons-lang, commons-logging + + + + + + org.springframework.ldap.control + + The ldap.control package contains an abstract + implementation of the DirContextProcessor interface that can be used as + a basis for processing RequestControls and ResponseControls. There is + also a concrete implementation that handles paged search results. The + LDAP Booster + Pack is used to get support for controls. + + + + Dependencies: ldap, ldap.core, LDAP booster pack (optional), spring-core, + commons-lang, commons-logging + + + + + + org.springframework.ldap.filter + + The ldap.filter package contains the Filter + abstraction and several implementations of it. + + + + Dependencies: ldap.core, commons-lang + + + + + + org.springframework.ldap.transaction.compensating + + The ldap.transaction.compensating package contains the + core LDAP-specific implementation of compensating transactions. + + + + Dependencies: ldap, ldap.core, transaction.compensating, + spring-core, commons-lang, commons-logging + + + + + + org.springframework.ldap.transaction.compensating.manager + + The ldap.transaction.compensating.manager package contains the + core implementation classes for client-side compensating transactions. + + + + Dependencies: ldap, ldap.core, ldap.transaction.compensating, + ldap.transaction.compensating.support, transaction.compensating, + spring-dao (optional), spring-jdbc (optional), commons-logging + + + + + + org.springframework.ldap.transaction.compensating.support + + The ldap.transaction.compensating.support package contains + useful helper classes for client-side compensating transactions. + + + + Dependencies: ldap.core, ldap.transaction.compensating + + + + + For the exact list of jar dependencies, see the Spring LDAP Ivy dependency manager descriptor + located within the Spring LDAP distribution at + spring-ldap/ivy.xml + + + + Support + + Spring LDAP 1.2.1 is supported on Spring 1.2.8 or later, including + 2.0.x. + + The community support forum is located at http://forum.springframework.org, + and the project web page is http://www.springframework.org/ldap. + + \ No newline at end of file diff --git a/mvn-build/core/docs/reference/src/pooling.xml b/mvn-build/core/docs/reference/src/pooling.xml new file mode 100644 index 00000000..2df9a509 --- /dev/null +++ b/mvn-build/core/docs/reference/src/pooling.xml @@ -0,0 +1,550 @@ + + + + Pooling Support + + + Introduction + + + Pooling LDAP connections helps mitigate the overhead of + creating a new LDAP connection for each LDAP interaction. + While + + Java LDAP pooling support + + exists it is limited in its configuration options and + features, such as connection validation and pool + maintenance. Spring LDAP provides support for detailed pool + configuration on a per- + ContextSource + basis. + + + + Pooling support is provided by + PoolingContextSource + which can wrap any + ContextSource + and pool both read-only and read-write + DirContext + objects. + + Jakarta Commons-Pool + + is used to provide the underlying pool implementation. + + + + + DirContext Validation + + + Validation of pooled connections is the primary motivation + for using a custom pooling library versus the JDK provided + LDAP pooling functionality. Validation allows pooled + DirContext + connections to be checked to ensure they are still properly + connected and configured when checking them out of the pool, + in to the pool or while idle in the pool + + + + The + DirContextValidator + interface is used by the + PoolingContextSource + for validation and + DefaultDirContextValidator + is provided as the default validation implementation. + DefaultDirContextValidator + does a + + DirContext.search(String, String, SearchControls) + + , with an empty name, a filter of + "objectclass=*" + and + SearchControls + set to limit a single result with the only the objectclass + attribute and a 500ms timeout. If the returned + NamingEnumeration + has results the + DirContext + passes validation, if no results are returned or an + exception is thrown the + DirContext + fails validation. The + DefaultDirContextValidator + should work with no configuration changes on most LDAP + servers and provide the fastest way to validate the + DirContext + . + + + + + Pool Properties + + + The following properties are available on the + PoolingContextSource + for configuration of the DirContext pool. The + contextSource + property must be set and the + dirContextValidator + property must be set if validation is enabled, all other + properties are optional. + + + + Pooling Configuration Properties + + + + + + + + + + Parameter + + Default + + Description + + + + + + + contextSource + + + + null + + + + The + ContextSource + implementation to get + DirContext + s from to populate the pool. + + + + + + dirContextValidator + + + + null + + + + The + DirContextValidator + implementation to use when validating + connections. This is required if + testOnBorrow + , + testOnReturn + , or + testWhileIdle + options are set to + true + . + + + + + + maxActive + + + + 8 + + + + The maximum number of active connections of + each type (read-only|read-write) that can be + allocated from this pool at the same time, + or non-positive for no limit. + + + + + + maxTotal + + + + -1 + + + + The overall maximum number of active + connections (for all types) that can be + allocated from this pool at the same time, + or non-positive for no limit. + + + + + + maxIdle + + + + 8 + + + + The maximum number of active connections of + each type (read-only|read-write) that can + remain idle in the pool, without extra ones + being released, or non-positive for no + limit. + + + + + + minIdle + + + + 0 + + + + The minimum number of active connections of + each type (read-only|read-write) that can + remain idle in the pool, without extra ones + being created, or zero to create none. + + + + + + maxWait + + + + -1 + + + + The maximum number of milliseconds that the + pool will wait (when there are no available + connections) for a connection to be returned + before throwing an exception, or + non-positive to wait indefinitely. + + + + + + whenExhaustedAction + + + + 1 (BLOCK) + + + + Specifies the behaviour when the pool is + exhausted. + + + + The + FAIL (0) + option will throw a + + NoSuchElementException + + when the pool is exhausted. + + + + + + The + BLOCK (1) + option will wait until a new + object is available. If + maxWait + is positive a + + NoSuchElementException + + is thrown if no new object is + available after the + maxWait + time expires. + + + + + + The + GROW (2) + option will create and return a + new object (essentially making + maxActive + meaningless). + + + + + + + + + testOnBorrow + + + + false + + + + The indication of whether objects will be + validated before being borrowed from the + pool. If the object fails to validate, it + will be dropped from the pool, and an + attempt to borrow another will be made. + + + + + + testOnReturn + + + + false + + + + The indication of whether objects will be + validated before being returned to the pool. + + + + + + testWhileIdle + + + + false + + + + The indication of whether objects will be + validated by the idle object evictor (if + any). If an object fails to validate, it + will be dropped from the pool. + + + + + + + timeBetweenEvictionRunsMillis + + + + + -1 + + + + The number of milliseconds to sleep between + runs of the idle object evictor thread. When + non-positive, no idle object evictor thread + will be run. + + + + + + numTestsPerEvictionRun + + + + 3 + + + + The number of objects to examine during each + run of the idle object evictor thread (if + any). + + + + + + + minEvictableIdleTimeMillis + + + + + 1000 * 60 * 30 + + + + The minimum amount of time an object may sit + idle in the pool before it is eligible for + eviction by the idle object evictor (if + any). + + + + +
+
+ + + Configuration + + + Configuring pooling should look very familiar if you're used + to Jakarta Commons-Pool or Commons-DBCP. You will first + create a normal + ContextSource + then wrap it in a + PoolingContextSource + . + + + ... + + + + + + + + + + + + ... + +]]> + + In a real world example you would probably configure the + pool options and enable connection validation; the above + serves as an example to demonstrate the general idea. + + + Ensure that the + pooled + property is set to + false + on any + ContextSource + that will be wrapped in a + PoolingContextSource + . The + PoolingContextSource + must be able to create new connections when needed + and if + pooled + is set to + true + that may not be possible. + + + + + You'll notice that the actual + ContextSource + gets an id with a "Target" suffix. The bean you will + actually refer to is the + PoolingContextSource + that wraps the target + contextSource + + + + + + Validation Configuration + + + Adding validation and a few pool configuration tweaks to + the above example is straight forward. Inject a + DirContextValidator + and set when validation should occur and the pool is + ready to go. + + + ... + + + + + + + + + + + + + + + + + ... + +]]> + + The above example will test each + DirContext + before it is passed to the client application and test + DirContext + s that have been sitting idle in the pool. + + + + + + Known Issues + + + Custom Authentication + + + The PoolingContextSource assumes that all + DirContext objects retrieved from + ContextSource.getReadOnlyContext() will have + the same environment and likewise that all + DirContext objects retrieved from + ContextSource.getReadWriteContext() will + have the same environment. This means that wrapping a + LdapContextSource configured with an + AuthenticationSource in a + PoolingContextSource will not function + as expected. The pool would be populated using the credentials + of the first user and unless new connections were needed + subsequent context requests would not be filled for the user + specified by the AuthenticationSource for + the requesting thread. + + + +
\ No newline at end of file diff --git a/mvn-build/core/docs/reference/src/preface.xml b/mvn-build/core/docs/reference/src/preface.xml new file mode 100644 index 00000000..b0a97605 --- /dev/null +++ b/mvn-build/core/docs/reference/src/preface.xml @@ -0,0 +1,32 @@ + + + Preface + + The Java Naming and Directory Interface (JNDI) is for LDAP programming + what Java Database Connectivity (JDBC) is for SQL programming. There are + several similarities between JDBC and JNDI/LDAP (Java LDAP). Despite + being two completely different APIs with different pros and cons, they + share a number of less flattering characteristics: + + + + They require extensive plumbing code, even to perform the simplest of tasks. + + + All resources need to be correctly closed, no matter what happens. + + + Exception handling is difficult. + + + + The above points often lead to massive code duplication in common usages + of the APIs. As we all know, code duplication is one of the worst code smells. + All in all, it boils down to this: JDBC and LDAP programming in Java are both + incredibly dull and repetitive. + + + Spring JDBC, a part of the Spring framework, provides excellent utilities for + simplifying SQL programming. We need a similar framework for Java LDAP programming. + + diff --git a/mvn-build/core/docs/reference/src/simple.xml b/mvn-build/core/docs/reference/src/simple.xml new file mode 100644 index 00000000..95056634 --- /dev/null +++ b/mvn-build/core/docs/reference/src/simple.xml @@ -0,0 +1,35 @@ + + + + Java 5 Support + + + SimpleLdapTemplate + + As of version 1.2 Spring LDAP includes the spring-ldap-tiger.jar distributable, which adds + a thin layer of Java 5 functionality on top of Spring LDAP. + + The SimpleLdapTemplate class adds search and lookup methods that take a + ParameterizedContextMapper, adding generics support to these methods. + + ParametrizedContextMapper is a typed version of ContextMapper, + which simplifies working with searches and lookups: + + Using <literal>ParameterizedContextMapper</literal> + public List<Person> getAllPersons(){ + return simpleLdapTemplate.search("", "(objectclass=person)", + new ParameterizedContextMapper<Person>() { + public Person mapFromContext(Object ctx) { + DirContextAdapter adapter = (DirContextAdapter) ctx; + Person person = new Person(); + // Fill the domain object with data from the DirContextAdapter + + return person; + } + }; +} + + + + + \ No newline at end of file diff --git a/mvn-build/core/docs/reference/src/transactions.xml b/mvn-build/core/docs/reference/src/transactions.xml new file mode 100644 index 00000000..b456e277 --- /dev/null +++ b/mvn-build/core/docs/reference/src/transactions.xml @@ -0,0 +1,216 @@ + + + + Transaction Support + + + Introduction + + Programmers used to working with relational databases coming to the LDAP + world often express surprise to the fact that there is no notion of transactions. + It is not specified in the protocol, and thus no servers support it. + Recognizing that this may be a major problem, Spring LDAP provides support for client-side, + compensating transactions on LDAP resources. + + LDAP transaction support is provided by ContextSourceTransactionManager, a + PlatformTransactionManager implementation that manages Spring transaction + support for LDAP operations. Along with its collaborators it keeps track of the LDAP operations + performed in a transaction, making record of the state before each operation and taking steps to + restore the initial state should the transaction need to be rolled back. + In addition to the actual transaction management, Spring LDAP transaction support also + makes sure that the same DirContext instance will be used throughout the same transaction, + i.e. the DirContext will not actually be closed until the transaction is finished, + allowing for more efficient resources usage. + + It is important to note that while the approach used by Spring LDAP to provide transaction support + is sufficient for many cases it is by no means "real" transactions in the traditional sense. + The server is completely unaware of the transactions, so e.g. if the connection is broken there will + be no hope to rollback the transaction. While this should be carefully considered it should also be noted + that the alternative will be to operate without any transaction support whatsoever; this is pretty much + as good as it gets. + The client side transaction support will add some overhead in addition to the work required + by the original operations. While this overhead should not be something to worry about in most cases, + if your application will not perform several LDAP operations within the same + transaction (e.g. a modifyAttributes followed by a rebind), or + if transaction synchronization with a JDBC data source is not required (see below) there will be nothing to gain + by using the LDAP transaction support. + + + + + Configuration + + Configuring Spring LDAP transactions should look very familiar if you're used to configuring Spring transactions. + You will create a TransactionManager instance and wrap your target object using a + TransactionProxyFactoryBean. In addition to this, you will also need to wrap your + ContextSource in a TransactionAwareContextSourceProxy. + + <beans> + ... + <bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource"> + <property name="url" value="ldap://localhost:389" /> + <property name="base" value="dc=example,dc=com" /> + <property name="userDn" value="cn=Manager" /> + <property name="password" value="secret" /> + </bean> + + <bean id="contextSource" + class="org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy"> + <constructor-arg ref="contextSourceTarget" /> + </bean> + + <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate"> + <constructor-arg ref="contextSource" /> + </bean> + + <bean id="transactionManager" + class="org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager"> + <constructor-arg ref="contextSource" /> + </bean> + + <bean id="myDataAccessObjectTarget" class="com.example.MyDataAccessObject"> + <property name="ldapTemplate" ref="ldapTemplate" /> + </bean> + + <bean id="myDataAccessObject" + class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> + <property name="transactionManager" ref="transactionManager" /> + <property name="target" ref="myDataAccessObjectTarget" /> + <property name="transactionAttributes"> + <props> + <prop key="*">PROPAGATION_REQUIRES_NEW</prop> + </props> + </property> + </bean> + ... + + In a real world example you would probably apply the transactions on the service object level + rather than the DAO level; the above serves as an example to demonstrate the general idea. + You'll notice that the actual ContextSource and DAO instances get ids with a + "Target" suffix. The beans you will actually refer to are the Proxies that are created + around the targets; contextSource and myDataAccessObject + + + + JDBC Transaction Integration + A common use case when working against LDAP is that some of the data is stored in the LDAP tree, but + other data is stored in a relational database. In this case, transaction support becomes even more important, + since the update of the different resources should be synchronized. + While actual XA transactions is not supported, support is provided to conceptually wrap JDBC and LDAP + access within the same transaction using the ContextSourceAndDataSourceTransactionManager. + A DataSource and a ContextSource is supplied to the + ContextSourceAndDataSourceTransactionManager, which will then manage the two transactions, + virtually as if they were one. When performing a commit, the LDAP part of the operation will always + be performed first, allowing both transactions to be rolled back should the LDAP commit fail. The JDBC + part of the transaction is managed exactly as in DataSourceTransactionManager, except that + nested transactions is not supported. + Once again it should be noted that the provided support is all client side. The wrapped transaction is not + an XA transaction. No two-phase as such commit is performed, as the LDAP server will be unable to vote on its outcome. + Once again, however, for the majority of cases the supplied support will be sufficient. + + + LDAP Compensating Transactions Explained + Spring LDAP manages compensating transactions by making record of the state in the LDAP tree + before each modifying operation (bind, unbind, rebind, + modifyAttributes, and rename). + This enables the system + to perform compensating operations should the transaction need to be rolled back. In many cases the + compensating operation is pretty straightforward. E.g. the compensating rollback operation for a + bind operation will quite obviously be to unbind the entry. Other operations however require + a different, more complicated approach because of some particular characteristics of LDAP databases. Specifically, + it is not always possible to get the values of all Attributes of an entry, making the above + strategy insufficient for e.g. an unbind operation. + This is why each modifying operation performed within a Spring LDAP managed transaction is internally + split up in four distinct operations - a recording operation, a preparation operation, a commit operation, + and a rollback operation. The specifics for each LDAP operation is described in the table below: + + + + + + + + + + LDAP Operation + Recording + Preparation + Commit + Rollback + + + + + bind + Make record of the DN of the entry to bind. + Bind the entry. + No operation. + Unbind the entry using the recorded DN. + + + rename + Make record of the original and target DN. + Rename the entry. + No operation. + Rename the entry back to its original DN. + + + unbind + Make record of the original DN and calculate a temporary DN. + Rename the entry to the temporary location. + Unbind the temporary entry. + Rename the entry from the temporary location back to its original DN. + + + rebind + Make record of the original DN and the new Attributes, and calculate a temporary DN. + Rename the entry to a temporary location. + Bind the new Attributes at the original DN, and unbind the original entry + from its temporary location. + Rename the entry from the temporary location back to its original DN. + + + modifyAttributes + Make record of the DN of the entry to modify and calculate compensating ModificationItems + for the modifications to be done. + Perform the modifyAttributes operation. + No operation. + Perform a modifyAttributes operation using the calculated compensating + ModificationItems. + + + +
+ A more detailed description of the internal workings of the Spring LDAP transaction support is available in the + javadocs. + + Renaming Strategies + As described in the table above, the transaction management of some operations require the original entry affected + by the operation to be temporarily renamed before the actual modification can be made in the commit. + The manner in which the temporary DN of the entry is calculated is managed by a TempEntryRenamingStrategy + supplied to the ContextSourceTransactionManager. Two implementations are supplied with Spring LDAP, + but if specific behaviour is required a custom implementation can easily be implemented by the user. The + provided TempEntryRenamingStrategy implementations are: + + DefaultTempEntryRenamingStrategy (the default). Adds a suffix to the least significant + part of the entry DN. E.g. for the DN cn=john doe, ou=users, this strategy would return the + temporary DN cn=john doe_temp, ou=users. The suffix is configurable using the tempSuffix + property + DifferentSubtreeTempEntryRenamingStrategy. Takes the least significant part of the DN + and appends a subtree DN to this. This makes all temporary entries be placed at a specific location in the LDAP tree. + The temporary subtree DN is configured using the subtreeNode property. E.g., if + subtreeNode is ou=tempEntries and the original DN of the entry is + cn=john doe, ou=users, the temporary DN will be cn=john doe, ou=tempEntries. + Note that the configured subtree node needs to be present in the LDAP tree. + + + There are some situations where the DefaultTempEntryRenamingStrategy will not work. E.g. if your are planning + to do recursive deletes you'll need to use DifferentSubtreeTempEntryRenamingStrategy. This is because + the recursive delete operation actually consists of a depth-first delete of each node in the sub tree individually. + Since it is not allowed to rename an entry that has any children, and DefaultTempEntryRenamingStrategy would + leave each node in the same subtree (with a different name) in stead of actually removing it, this operation would fail. + When in doubt, use DifferentSubtreeTempEntryRenamingStrategy. + + +
+
\ No newline at end of file diff --git a/mvn-build/core/docs/reference/styles/fopdf.xsl b/mvn-build/core/docs/reference/styles/fopdf.xsl new file mode 100644 index 00000000..d47be44b --- /dev/null +++ b/mvn-build/core/docs/reference/styles/fopdf.xsl @@ -0,0 +1,475 @@ + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright ©right; 2005-2006 + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -5em + -5em + + + + + + + + + + + Spring LDAP () + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bold + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + 1 + + 1 + + + + + + book toc + + + + 2 + + + + + + + + + + 0 + 0 + 0 + + + 5mm + 10mm + 10mm + + 15mm + 10mm + 0mm + + 18mm + 18mm + + + 0pc + + + + + justify + false + + + 11 + 8 + + + 1.4 + + + + + + + 0.8em + + + + + + 17.4cm + + + + 4pt + 4pt + 4pt + 4pt + + + + 0.1pt + 0.1pt + + + + + 1 + + + + + + + + left + bold + + + pt + + + + + + + + + + + + + + + 0.8em + 0.8em + 0.8em + + + pt + + 0.1em + 0.1em + 0.1em + + + 0.6em + 0.6em + 0.6em + + + pt + + 0.1em + 0.1em + 0.1em + + + 0.4em + 0.4em + 0.4em + + + pt + + 0.1em + 0.1em + 0.1em + + + + + bold + + + pt + + false + 0.4em + 0.6em + 0.8em + + + + + + + + + pt + + + + + 1em + 1em + 1em + #444444 + solid + 0.1pt + 0.5em + 0.5em + 0.5em + 0.5em + 0.5em + 0.5em + + + + 1 + + #F0F0F0 + + + + + + 0 + 1 + + + 90 + + + + + '1' + &admon_gfx_path; + + + + + + figure after + example before + equation before + table before + procedure before + + + + 1 + + + + 0.8em + 0.8em + 0.8em + 0.1em + 0.1em + 0.1em + + + + + + + + + + + + + + + + + diff --git a/mvn-build/core/docs/reference/styles/html.css b/mvn-build/core/docs/reference/styles/html.css new file mode 100644 index 00000000..c603be90 --- /dev/null +++ b/mvn-build/core/docs/reference/styles/html.css @@ -0,0 +1,270 @@ +BODY { + text-align: justify; + margin-right: 2em; + margin-left: 2em; +} + +A[accesskey^="p"] { + font-family: Verdana, Arial, helvetica, sans-serif; + font-size: 12px; + color: #003399; +} + +A[accesskey^="u"] { + font-family: Verdana, Arial, helvetica, sans-serif; + font-size: 12px; + color: #003399; +} + +A[accesskey^="h"] { + font-family: Verdana, Arial, helvetica, sans-serif; + font-size: 12px; + color: #003399; +} + +A[accesskey^="n"] { + font-family: Verdana, Arial, helvetica, sans-serif; + font-size: 12px; + color: #003399; +} + +A { + color: #003399; + font-family: Verdana, Arial, helvetica, sans-serif; + font-size: 12px; +} + +A:active { + color: #003399; +} + +A:visited { + color: #888888; +} + +P { + font-family: Verdana, Arial; +} + +DT { + font-family: Verdana, Arial; + font-size: 12px; +} + +P, DL, DT, DD, BLOCKQUOTE { + color: #000000; + margin-bottom: 3px; + margin-top: 3px; + padding-top: 0px; +} + +OL, UL, P { + margin-top: 6px; + margin-bottom: 6px; +} + +P, BLOCKQUOTE { + font-size: 90%; +} + +P.releaseinfo { + font-size: 100%; + font-weight: bold; + font-family: Verdana, Arial, helvetica, sans-serif; + padding-top: 10px; +} + +P.pubdate { + font-size: 120%; font-weight: bold; + font-family: Verdana, Arial, helvetica, sans-serif; +} + +td { + font-size: 80%; +} + +TD, TH, SPAN { + color: #000000; +} + +TD[width^="40%"] { + font-family: Verdana, Arial, helvetica, sans-serif; + font-size: 12px; + color: #003399; +} + +TABLE[summary^="Navigation header"] tbody tr th[colspan^="3"] { + font-family: Verdana, Arial, helvetica, sans-serif; +} + +BLOCKQUOTE { + margin-right: 0px; +} + +H1, H2, H3, H4, H5, H6 { + color: #000000; + font-weight:500; + margin-top:0px; + padding-top:14px; + font-family: Verdana, Arial, helvetica, sans-serif; + margin-bottom: 0px; +} + +H2.title { + font-weight:800; + margin-bottom: 8px; +} + +H2.subtitle { + font-weight:800; + margin-bottom: 20px; +} + +.firstname, .surname { + font-size: 12px; + font-family: Verdana, Arial, helvetica, sans-serif; +} + +TABLE { + border-collapse: collapse; + border-spacing:0; + border: 1px thin black; + empty-cells: hide; + margin: 10px 0px 30px 50px; + width: 90%; +} + +div.table { + margin: 30px 0 30 0; + border: 1px dashed gray; + padding: 10px; +} + +div.table > p.title { + padding-left: 10px; +} + +table[summary^="Navigation footer"] { + border-collapse: collapse; + border-spacing:0; + border: 1px thin black; + empty-cells: hide; + margin: 0px; + width: 100%; +} + +table[summary^="Note"], table[summary^="Warning"], table[summary^="Tip"] { + border-collapse: collapse; + border-spacing:0; + border: 1px thin black; + empty-cells: hide; + margin: 10px 0 10 -20; + width: 100%; +} + +TD { + padding: 4pt; + font-family: Verdana, Arial, helvetica, sans-serif; +} + +div.warning TD { + text-align: justify; +} + +H1 { + font-size: 150%; +} +H2 { + font-size: 110%; +} +H3 { + font-size: 100%; font-weight: bold; +} +H4 { + font-size: 90%; font-weight: bold; +} +H5 { + font-size: 90%; font-style: italic; +} +H6 { + font-size: 100%; font-style: italic; +} + +TT { + font-size: 110%; + font-family: "Courier New", Courier, monospace; + color: #000000; +} + +.navheader, .navfooter { + background-color: #e4eff3; +} + +PRE { + font-size: 110%; + padding: 5px; + border-style: solid; + border-width: 1px; + border-color: #CCCCCC; + background-color: #F4F4F4; +} + +UL, OL, LI { + list-style: disc; +} + +HR { + width: 100%; + height: 1px; + background-color: #CCCCCC; + border-width: 0px; + padding: 0px; + color: #CCCCCC; +} + +.variablelist { + padding-top: 10; + padding-bottom:10; + margin:0; +} + +.term { + font-weight:bold; +} + +.mediaobject { + padding-top: 30px; + padding-bottom: 30px; +} + +.legalnotice { + font-family: Verdana, Arial, helvetica, sans-serif; + font-size: 12px; + font-style: italic; +} + +.sidebar { + float: right; + margin: 10px 0px 10px 30px; + padding: 10px 20px 20px 20px; + width: 33%; + border: 1px solid black; + background-color: #F4F4F4; + font-size: 14px; +} + +.programlisting * .classname { + font-size: 95%; +} + +.programlisting * .interfacename { + font-size: 95%; +} + +.programlisting * .literal { + font-size: 95%; +} + +.property { + font-family: "Courier New", Courier, monospace; +} diff --git a/mvn-build/core/docs/reference/styles/html.xsl b/mvn-build/core/docs/reference/styles/html.xsl new file mode 100644 index 00000000..0b35acad --- /dev/null +++ b/mvn-build/core/docs/reference/styles/html.xsl @@ -0,0 +1,100 @@ + + + + + +]> + + + + + + + + ../styles/html.css + + + 1 + 0 + 1 + 0 + + + + + + book toc + + + + 3 + + + + + 1 + + + + + + + 1 + &callout_gfx_path; + + + 90 + + + + + '1' + &admon_gfx_path; + + + + + figure after + example before + equation before + table before + procedure before + + + + , + + + + + + + + +
+

Authors

+

+ +

+
+ +
diff --git a/mvn-build/core/docs/reference/styles/html_chunk.xsl b/mvn-build/core/docs/reference/styles/html_chunk.xsl new file mode 100644 index 00000000..258b97ce --- /dev/null +++ b/mvn-build/core/docs/reference/styles/html_chunk.xsl @@ -0,0 +1,103 @@ + + + + + +]> + + + + '5' + '1' + ../styles/html.css + + 1 + 0 + 1 + 0 + + + + book toc + + + 3 + + + 1 + + + + + 1 + &callout_gfx_path; + + 90 + + + '1' + &admon_gfx_path; + + + + figure after + example before + equation before + table before + procedure before + + + + , + + + + + + + + +
+

Authors

+

+ +

+
+ + + + +
+ + + + + 1 + + diff --git a/mvn-build/core/docs/reference/styles/tld.to.docbook.xsl b/mvn-build/core/docs/reference/styles/tld.to.docbook.xsl new file mode 100644 index 00000000..b0823a14 --- /dev/null +++ b/mvn-build/core/docs/reference/styles/tld.to.docbook.xsl @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + -intro + + Introduction + + + + This appendix describes the + + + + tag library descriptor. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The + + + + tag + + + + + + + Attributes + + + + 4 + + + + center + + + + + center + + + + + center + + + + + center + + + + + + + center + + Attribute + + + + center + + Required? + + + + center + + Runtime Expression? + + + + center + + Description + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + + diff --git a/mvn-build/core/docs/reference/styles/xsd.to.docbook.xsl b/mvn-build/core/docs/reference/styles/xsd.to.docbook.xsl new file mode 100644 index 00000000..4a9bb86d --- /dev/null +++ b/mvn-build/core/docs/reference/styles/xsd.to.docbook.xsl @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + -intro + + + Introduction + + + + + This appendix describes the + + + + schema. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The + + + + element + + + [TODO : insert the description of the element here] + + + + + + + . + + + diff --git a/mvn-build/core/docs/upgrade/src/readme.txt b/mvn-build/core/docs/upgrade/src/readme.txt new file mode 100644 index 00000000..fecf1c5c --- /dev/null +++ b/mvn-build/core/docs/upgrade/src/readme.txt @@ -0,0 +1,19 @@ +This project uses the 'DocBook XSL distribution' for HTML and PDF +generation of project upgrade documentation. + +This project's build.xml file contains targets to generate the +project upgrade documentation. + +To generate project documentation, execute one of the following +build targets: + +* upgrade-all - generate documentation in all supported formats +* upgrade-pdf - generate the PDF documentation +* upgrade-html - generate the HTML documentation +* upgrade-clean - clean any output directories for docs + +For generation to complete successfully, you must have first extracted +the .jar libraries contained in this archive: + - http://static.springframework.org/spring/files/docbook-reference-libs.zip +... to ${basedir}/docs/reference. If you have not yet done so, download this file +and unzip the contents of the archive into ${basedir}/docs/reference. diff --git a/mvn-build/core/docs/upgrade/src/upgrade.xml b/mvn-build/core/docs/upgrade/src/upgrade.xml new file mode 100644 index 00000000..1e947dcf --- /dev/null +++ b/mvn-build/core/docs/upgrade/src/upgrade.xml @@ -0,0 +1,460 @@ + + +
+ Upgrade Guide + + The base package for Spring LDAP is + org.springframework.ldap. In this document, we will refer + to this package as ldap. + + + Upgrading from 1.1.x to 1.2 + + In 1.2, there are a couple of major changes: + + + + Transaction support added + + + + Exception hierarchy changed + + + + Package structure changed + + + + The transaction support is entirely new and should not affect users of + previous releases. + + The exception hierarchy was previously based on the Spring + DataAccessException hierarchy, but is now an unchecked + mirror of the JNDI NamingException hierarchy. The fact + that it is unchecked like DataAccessException makes + migration less troublesome. However, note that + ContextSource and its implementations now throw + org.springframework.ldap.NamingException, not + DataAccessException. + + In release 1.1.x, there were two cyclic dependencies. The first one + was between ldap.support and + ldap.support.parser, while the other one was between + ldap and ldap.support. + + The first cycle was easily solved by changing the generation of the + parser to generate code into ldap.support rather than + into a separate package. + + The second cycle was slightly more complicated. To sort it out, some major + restructuring was needed, as documented in the following sections. + + While the package restructuring is rather extensive, no classes have + changed names - this means that for the most cases a simple import resolve operation + (such as "Ctrl+Shift-O" in Eclipse) should sort things out. + You will still need to change the package names manually in your configuration files - + this should be a simple find/replace operation based on the information below. + + + Moved core classes from ldap to ldap.core + + The core classes listed below were moved to a subpackage called + ldap.core. + + + + + + + + Class + + Before + + After + + + + + + AttributesMapper + + ldap + + ldap.core + + + + AuthenticationSource + + ldap + + ldap.core + + + + CollectingNameClassPair- CallbackHandler + + ldap + + ldap.core + + + + ContextAssembler + + ldap + + ldap.core + + + + ContextExecutor + + ldap + + ldap.core + + + + ContextMapper + + ldap + + ldap.core + + + + ContextSource + + ldap + + ldap.core + + + + DefaultNameClassPairMapper + + ldap + + ldap.core + + + + DirContextProcessor + + ldap + + ldap.core + + + + LdapOperations + + ldap + + ldap.core + + + + LdapTemplate + + ldap + + ldap.core + + + + NameClassPairCallbackHandler + + ldap + + ldap.core + + + + NameClassPairMapper + + ldap + + ldap.core + + + + SearchExecutor + + ldap + + ldap.core + + + + + + + + Moved subpackages of ldap.support to ldap + + The various packages for authentication, controls and filters were + moved to the top level ldap package. + + + + + + + + Class + + Before + + After + + + + + + * + + ldap.support.authentication + + ldap.authentication + + + + * + + ldap.support.control + + ldap.control + + + + * + + ldap.support.filter + + ldap.filter + + + + + + + + Moved most classes in ldap.support to ldap.core or + ldap.core.support + + The classes in ldap.support that are directly + referenced from the core classes were moved to + ldap.core, while various support classes not directly + referenced were moved to ldap.core.support. + + + + + + + + Class + + Before + + After + + + + + + AbstractContextSource + + ldap.support + + ldap.core.support + + + + AggregateDirContextProcessor + + ldap.support + + ldap.core.support + + + + AttributeModificationsAware + + ldap.support + + ldap.core + + + + CountNameClassPair- CallbackHandler + + ldap.support + + ldap.core.support + + + + DefaultDirObjectFactory + + ldap.support + + ldap.core + + + + DefaultDnParserFactory + + ldap.support + + ldap.core + + + + DirContextAdapter + + ldap.support + + ldap.core + + + + DirContextOperations + + ldap.support + + ldap.core + + + + DirContextSource + + ldap.support + + ldap.core.support + + + + DistinguishedName + + ldap.support + + ldap.core + + + + DnParser + + ldap.support + + ldap.core + + + + LdapContextSource + + ldap.support + + ldap.core.support + + + + LdapEncoder + + ldap.support + + ldap.core + + + + LdapRdn + + ldap.support + + ldap.core + + + + LdapRdnComponent + + ldap.support + + ldap.core + + + + + + + + Moved classes in ldap and ldap.util to ldap.support + + The ldap.util package has been removed. The + classes in ldap.util were moved to + ldap.support. Also, the classes in + ldap related to exception translation were moved to + ldap.support. + + + The translation classes are deprecated. Instead, the static + method LdapUtils.convertLdapException should be + used. + + + + + + + + + Class + + Before + + After + + + + + + DefaultNamingExceptionTranslator + + ldap + + ldap.support + + + + LdapUtils + + - + + ldap.support + + + + ListComparator + + ldap.util + + ldap.support + + + + NamingExceptionTranslator + + ldap + + ldap.support + + + + + + +
diff --git a/mvn-build/core/itest-openldap-targets.xml b/mvn-build/core/itest-openldap-targets.xml new file mode 100644 index 00000000..09e00cbc --- /dev/null +++ b/mvn-build/core/itest-openldap-targets.xml @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + compiling itest-openldap sources + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Integration test report has been generated. +The HTML report is ${target.itest.openldap.results.html.dir}/index.html + + + + + diff --git a/mvn-build/core/ivy.xml b/mvn-build/core/ivy.xml new file mode 100644 index 00000000..899e1aef --- /dev/null +++ b/mvn-build/core/ivy.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/core/license.txt b/mvn-build/core/license.txt new file mode 100644 index 00000000..0c8a028d --- /dev/null +++ b/mvn-build/core/license.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2006 Mattias Arthursson and Ulrik Sandberg + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/mvn-build/core/notice.txt b/mvn-build/core/notice.txt new file mode 100644 index 00000000..4e701033 --- /dev/null +++ b/mvn-build/core/notice.txt @@ -0,0 +1,16 @@ + ====================================================================== + == NOTICE file corresponding to section 4 d of the Apache License, == + == Version 2.0, for the LdapTemplate distribution. == + ====================================================================== + + This product includes software developed by + the Apache Software Foundation (http://www.apache.org). + + The end-user documentation included with a redistribution, if any, + must include the following acknowledgement: + + "This product includes software developed by the LdapTemplate + Project (http://ldaptemplate.sourceforge.net)." + + Alternately, this acknowledgement may appear in the software itself, + if and wherever such third-party acknowledgements normally appear. diff --git a/mvn-build/core/pom.xml b/mvn-build/core/pom.xml new file mode 100644 index 00000000..70aece06 --- /dev/null +++ b/mvn-build/core/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + org.springframework.ldap + spring-ldap + Spring LDAP Core + 1.2.2-SNAPSHOT + + 2.0.8 + + + + + + maven-compiler-plugin + + 1.4 + 1.4 + + + + org.codehaus.mojo + javacc-maven-plugin + 2.4 + + + + javacc + + + + + + + + + + + commons-logging + commons-logging + 1.0.4 + + + commons-lang + commons-lang + 2.1 + + + commons-pool + commons-pool + 1.3 + provided + + + com.sun + ldapbp + 1.0 + system + ${basedir}/../lib/ldapbp.jar + + + org.acegisecurity + acegi-security + 1.0.3 + provided + + + + org.springframework + spring-core + ${spring.version} + + + org.springframework + spring-beans + ${spring.version} + + + org.springframework + spring-context + ${spring.version} + provided + + + org.springframework + spring-dao + ${spring.version} + provided + + + org.springframework + spring-jdbc + ${spring.version} + provided + + + + junit + junit + 3.8.2 + test + + + easymock + easymock + 1.2_Java1.3 + test + + + gsbase + gsbase + 2.0.1 + test + + + diff --git a/mvn-build/core/project.properties b/mvn-build/core/project.properties new file mode 100644 index 00000000..1b09ab76 --- /dev/null +++ b/mvn-build/core/project.properties @@ -0,0 +1,17 @@ +# properties defined in this file are overridable by a local build.properties in this project dir + +# The location of the common build system +common.build.dir=${basedir}/../common-build + +src.javacc.dir=${basedir}/src/main/javacc +javacc.home.dir=c:/java/javacc-4.0 + +project.base.version=1.2.2-SNAPSHOT +#project.version=${project.base.version} +#ivy.status=release + +javac.source=1.3 +javac.target=1.3 + +# extend common-build with source capability +main.build.configs=global,buildtime,test,source diff --git a/mvn-build/core/project.xml b/mvn-build/core/project.xml new file mode 100644 index 00000000..683ca71c --- /dev/null +++ b/mvn-build/core/project.xml @@ -0,0 +1,240 @@ + + + 3 + + spring-ldap + Spring LDAP + org.springframework + spring-ldap + 1.1 + http://opensource.atlassian.com/projects/spring/browse/LDAP + 2006 + org.springframework.ldap + + <p> + Spring LDAP is a library to simplify LDAP programming in + Java, built on the same principles as Spring JDBC. + </p> + <p> + The LdapTemplate class encapsulates all the plumbing work + involved in traditional LDAP programming, such as creating + contexts, looping through NamingEnumerations, handling + Exceptions and cleaning up resources. This leaves the programmer + to handle the important stuff - where to find data (DNs and + Filters) and what to do with it (map to and from domain objects, + bind, modify, unbind, etc.), in the same way that JdbcTemplate + relieves the programmer from everything but the actual SQL and + how the data maps to the domain model. + </p> + <p> + In addition to this, LdapTemplate provides Exception translation + from NamingExceptions to DataAccessExceptions, as well as + several utilities for working with filters, LDAP paths and + Attributes. + </p> + + + Java framework for easier LDAP programming + + http://www.springframework.org/ldap + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + scm:svn:https://svn.sourceforge.net/svnroot/springframework/spring-projects/trunk/spring-ldap + + http://svn.sourceforge.net/viewvc/springframework/spring-projects/ + + + + Mattias Arthursson + marthursson + mattias-DOT-arthursson-AT-jayway-dot-se + Jayway AB + http://www.jayway.se + 1 + + Project Lead + SourceForge Admin + Committer + + + + Ulrik Sandberg + ulsa + ulrik_DOT_sandberg-AT-jayway_DOT_se + Jayway AB + http://www.jayway.se + 1 + + Project Lead + SourceForge Admin + Committer + + + + + + Gregor Middell + + + + Jayway AB + http://www.jayway.se + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + manual + + + + + junit + junit + 3.8.1 + jar + + + commons-logging + commons-logging + 1.0.4 + jar + + + commons-lang + commons-lang + 2.1 + jar + + + commons-collections + commons-collections + 3.1 + jar + + + easymock + easymock + 1.1 + jar + + + easymock + easymockclassextension + 1.1 + jar + + + springframework + spring-core + 1.2.6 + jar + + + springframework + spring-context + 1.2.6 + jar + + + springframework + spring-beans + 1.2.6 + jar + + + springframework + spring-dao + 1.2.6 + jar + + + cglib + cglib-full + 2.0.1 + jar + + + gsbase + gsbase + 2.0 + jar + + + acegisecurity + acegi-security + 1.0.0-RC2 + jar + + + + mattias.arthursson@jayway.se + src/java + src/test + + + **/*Test.java + + + + src/test + + **/*.dtd + **/*.xls + **/*.xml + **/*.properties + + false + + + + + + src/java + + **/*.dtd + **/*.xml + **/*.properties + **/*.xls + + false + + + src/test + + **/*.dtd + **/*.xml + **/*.properties + **/*.xls + + false + + + + + + maven-changes-plugin + + + maven-faq-plugin + + maven-javadoc-plugin + maven-jdepend-plugin + maven-jcoverage-plugin + maven-junit-report-plugin + maven-jxr-plugin + maven-license-plugin + + + + + + + \ No newline at end of file diff --git a/mvn-build/core/readme.txt b/mvn-build/core/readme.txt new file mode 100644 index 00000000..b58d4657 --- /dev/null +++ b/mvn-build/core/readme.txt @@ -0,0 +1,87 @@ +Spring-LDAP 1.2 (Oct 2007) +----------------------------- +http://www.springframework.org/ldap + +1. INTRODUCTION + +Spring-LDAP is a library to simplify LDAP programming in Java, built on the same +principles as Spring Jdbc. + +The LdapTemplate class encapsulates all the plumbing work involved in traditional LDAP +programming, such as creating, looping through NamingEnumerations, handling Exceptions +and cleaning up resources. This leaves the programmer to handle the important stuff - +where to find data (DNs and Filters) and what do do with it (map to and from domain +objects, bind, modify, unbind, etc.), in the same way that JdbcTemplate relieves the +programmer of all but the actual SQL and how the data maps to the domain model. + +In addition to this, Spring-LDAP provides Exception translation from NamingExceptions +to an unchecked exception hirearchy, as well as several utilities for working with filters, +LDAP paths and Attributes. + +As of version 1.2, support for client-side compensating transaction is provided, as well as +Java 5 generics support with the SimpleLdapTemplate. + +2. RELEASE INFO + +Spring LDAP requires J2SE 1.4 for running. +J2SE 1.4, and javacc version 4.0 is required for building the Spring LDAP distributables from source code. +J2EE 1.4 (Servlet 2.3, JSP 1.2) is required for running the example. + +The Spring-LDAP release comes in three different distributables: +* spring-ldap-bin-x.x.zip + ** readme.txt - this readme file. + ** license.txt - terms and conditions for use, reproduction and distribution + ** dist/spring-ldap-x.x.jar - the library + ** dist/spring-ldap-src-x.x.zip - the source code of the library + ** dist/ivys - ivy configuration + ** doc/api - api javadoc documentation + ** doc/reference - full project reference documentation + ** doc/upgrade - Upgrade guide from 1.1.2 + +* spring-ldap-bin-with-dependencies-x.x.zip + ** readme.txt - this readme file. + ** license.txt - terms and conditions for use, reproduction and distribution + ** dist/spring-ldap-x.x.jar - the library + ** dist/spring-ldap-src-x.x.zip - the source code of the library + ** dist/ivys - ivy configuration + ** lib - all referenced libraries necessary to use spring-LDAP + ** doc/api - api javadoc documentation + ** doc/reference - full project reference documentation + ** doc/upgrade - Upgrade guide from 1.1.2 + +The sources included in the distributables are for reference use only, however buildable +sources are available in svn on sourceforge on the following URLs: +Sources for this release (1.2 tag): +https://springframework.svn.sourceforge.net/svnroot/springframework/spring-ldap/tags/1.2 +Latest sources (trunk): +https://springframework.svn.sourceforge.net/svnroot/springframework/spring-ldap/trunk + +Nightly builds are available for download from: +http://static.springframework.org/downloads/nightly/snapshot-download.php?project=LDAP + +Spring-LDAP is released under the terms of the Apache Software License (see license.txt). + +3. DISTRIBUTION JAR FILES + +The following distinct jar files are included in the distribution. This list +specifies the respective contents and third-party dependencies. + +* spring-ldap-x.x.jar +- Contents: The Spring-LDAP library +- Dependencies: Commons Logging, Commons Lang, spring-beans, + spring-core, spring-context, spring-dao, ldapbp, commons-pool + +* spring-ldap-tiger-x.x.jar +- Contents: The Spring-LDAP Java 5 support library +- Dependencies: Commons Logging, Commons Lang, spring-beans, + spring-core, spring-context, spring-dao, ldapbp, commons-pool + +4. WHERE TO START + +The distribution contains extensive JavaDoc documentation as well as full reference +documentation and a sample application illustrating different ways to use Spring-LDAP. +The Spring-LDAP homepage can be found at the following URL: + +http://www.springframework.org/ldap + +There you will find resources related to the project. diff --git a/mvn-build/core/src/itest-openldap/java/conf/ldap-openldap.properties b/mvn-build/core/src/itest-openldap/java/conf/ldap-openldap.properties new file mode 100644 index 00000000..427661f7 --- /dev/null +++ b/mvn-build/core/src/itest-openldap/java/conf/ldap-openldap.properties @@ -0,0 +1,4 @@ +urls=ldap://127.0.0.1 +userDn=cn=manager,dc=jayway,dc=se +password=secret +base=dc=jayway,dc=se diff --git a/mvn-build/core/src/itest-openldap/java/conf/ldap.conf b/mvn-build/core/src/itest-openldap/java/conf/ldap.conf new file mode 100644 index 00000000..b4c2f37d --- /dev/null +++ b/mvn-build/core/src/itest-openldap/java/conf/ldap.conf @@ -0,0 +1,28 @@ +# +# LDAP Defaults +# + +# See ldap.conf(5) for details +# This file should be world readable but not world writable. + +#BASE dc=example, dc=com +#URI ldap://ldap.example.com ldap://ldap-master.example.com:666 + +#SIZELIMIT 12 +#TIMELIMIT 15 +#DEREF never + +include "c:/Program/OpenLDAP/schema/core.schema" +include "c:/Program/OpenLDAP/schema/cosine.schema" +include "c:/Program/OpenLDAP/schema/inetorgperson.schema" +database bdb +suffix "dc=jayway,dc=se" +rootdn "cn=Manager,dc=jayway,dc=se" +rootpw secret +directory "c:/Program/OpenLDAP/data" + +TLSCipherSuite HIGH:MEDIUM:+SSLv2 +TLSCACertificateFile /tmp/cacert.pem +TLSCertificateFile /tmp/servercrt.pem +TLSCertificateKeyFile /tmp/serverkey.pem +TLSVerifyClient never diff --git a/mvn-build/core/src/itest-openldap/java/conf/ldapTemplateTestContext-openldap.xml b/mvn-build/core/src/itest-openldap/java/conf/ldapTemplateTestContext-openldap.xml new file mode 100644 index 00000000..fb18a69d --- /dev/null +++ b/mvn-build/core/src/itest-openldap/java/conf/ldapTemplateTestContext-openldap.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/core/src/itest-openldap/java/org/springframework/ldap/LdapTemplateLookupOpenLdapITest.java b/mvn-build/core/src/itest-openldap/java/org/springframework/ldap/LdapTemplateLookupOpenLdapITest.java new file mode 100644 index 00000000..11deccf1 --- /dev/null +++ b/mvn-build/core/src/itest-openldap/java/org/springframework/ldap/LdapTemplateLookupOpenLdapITest.java @@ -0,0 +1,220 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.ContextMapper; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +/** + * Tests the lookup methods of LdapTemplate on OpenLdap. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public class LdapTemplateLookupOpenLdapITest extends + AbstractDependencyInjectionSpringContextTests { + + private LdapTemplate tested; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext-openldap.xml" }; + } + + /** + * This method depends on a DirObjectFactory ({@link org.springframework.ldap.core.support.DefaultDirObjectFactory}) + * being set in the ContextSource. + */ + public void testLookup_Plain() { + DirContextAdapter result = (DirContextAdapter) tested + .lookup("cn=Some Person2, ou=company1,c=Sweden"); + + assertEquals("Some Person2", result.getStringAttribute("cn")); + assertEquals("Person2", result.getStringAttribute("sn")); + assertEquals("Sweden, Company1, Some Person2", result + .getStringAttribute("description")); + } + + public void testLookup_AttributesMapper() { + AttributesMapper mapper = new PersonAttributesMapper(); + Person person = (Person) tested.lookup( + "cn=Some Person2, ou=company1,c=Sweden", mapper); + + assertEquals("Some Person2", person.getFullname()); + assertEquals("Person2", person.getLastname()); + assertEquals("Sweden, Company1, Some Person2", person.getDescription()); + } + + public void testLookup_AttributesMapper_DistinguishedName() { + AttributesMapper mapper = new PersonAttributesMapper(); + Person person = (Person) tested.lookup(new DistinguishedName( + "cn=Some Person2, ou=company1,c=Sweden"), mapper); + + assertEquals("Some Person2", person.getFullname()); + assertEquals("Person2", person.getLastname()); + assertEquals("Sweden, Company1, Some Person2", person.getDescription()); + } + + /** + * An {@link AttributesMapper} that only maps a subset of the full + * attributes list. Used in tests where the return attributes list has been + * limited. + * + * @author Ulrik Sandberg + */ + private final class SubsetPersonAttributesMapper implements + AttributesMapper { + /** + * Maps the cn attribute into a {@link Person} object. + * Also verifies that the other attributes haven't been set. + * + * @see org.springframework.ldap.core.AttributesMapper#mapFromAttributes(javax.naming.directory.Attributes) + */ + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + Person person = new Person(); + person.setFullname((String) attributes.get("cn").get()); + assertNull("sn should be null", attributes.get("sn")); + assertNull("description should be null", attributes + .get("description")); + return person; + } + } + + /** + * Verifies that only the subset is used when specifying a subset of the + * available attributes as return attributes. + */ + public void testLookup_ReturnAttributes_AttributesMapper() { + AttributesMapper mapper = new SubsetPersonAttributesMapper(); + + Person person = (Person) tested.lookup( + "cn=Some Person2, ou=company1,c=Sweden", new String[] { "cn" }, + mapper); + + assertEquals("Some Person2", person.getFullname()); + assertNull("lastName should not be set", person.getLastname()); + assertNull("description should not be set", person.getDescription()); + } + + /** + * Verifies that only the subset is used when specifying a subset of the + * available attributes as return attributes. Uses DistinguishedName instead + * of plain string as name. + */ + public void testLookup_ReturnAttributes_AttributesMapper_DistinguishedName() { + AttributesMapper mapper = new SubsetPersonAttributesMapper(); + Person person = (Person) tested.lookup(new DistinguishedName( + "cn=Some Person2, ou=company1,c=Sweden"), + new String[] { "cn" }, mapper); + + assertEquals("Some Person2", person.getFullname()); + assertNull("lastName should not be set", person.getLastname()); + assertNull("description should not be set", person.getDescription()); + } + + /** + * This method depends on a DirObjectFactory ({@link org.springframework.ldap.core.support.DefaultDirObjectFactory}) + * being set in the ContextSource. + */ + public void testLookup_ContextMapper() { + ContextMapper mapper = new PersonContextMapper(); + Person person = (Person) tested.lookup( + "cn=Some Person2, ou=company1,c=Sweden", mapper); + + assertEquals("Some Person2", person.getFullname()); + assertEquals("Person2", person.getLastname()); + assertEquals("Sweden, Company1, Some Person2", person.getDescription()); + } + + /** + * Verifies that only the subset is used when specifying a subset of the + * available attributes as return attributes. + */ + public void testLookup_ReturnAttributes_ContextMapper() { + ContextMapper mapper = new PersonContextMapper(); + + Person person = (Person) tested.lookup( + "cn=Some Person2, ou=company1,c=Sweden", new String[] { "cn" }, + mapper); + + assertEquals("Some Person2", person.getFullname()); + assertNull("lastName should not be set", person.getLastname()); + assertNull("description should not be set", person.getDescription()); + } + + /** + * Verifies that we can lookup an entry that has a multi-valued rdn, which + * means more than one attribute is part of the relative DN for the entry. + */ + public void testLookup_MultiValuedRdn() { + AttributesMapper mapper = new PersonAttributesMapper(); + Person person = (Person) tested.lookup( + "cn=Some Person+sn=Person, ou=company1,c=Norway", mapper); + + assertEquals("Some Person", person.getFullname()); + assertEquals("Person", person.getLastname()); + assertEquals("Norway, Company1, Some Person+Person", person + .getDescription()); + } + + /** + * Verifies that we can lookup an entry that has a multi-valued rdn, which + * means more than one attribute is part of the relative DN for the entry. + */ + public void testLookup_MultiValuedRdn_DirContextAdapter() { + DirContextAdapter result = (DirContextAdapter) tested + .lookup("cn=Some Person+sn=Person, ou=company1,c=Norway"); + + assertEquals("Some Person", result.getStringAttribute("cn")); + assertEquals("Person", result.getStringAttribute("sn")); + assertEquals("Norway, Company1, Some Person+Person", result + .getStringAttribute("description")); + } + + public void testLookup_GetNameInNamespace_Plain() { + DirContextAdapter result = (DirContextAdapter) tested + .lookup("cn=Some Person2, ou=company1,c=Sweden"); + + assertEquals("cn=Some Person2, ou=company1, c=Sweden", result.getDn() + .toString()); + assertEquals( + "cn=Some Person2, ou=company1, c=Sweden, dc=jayway, dc=se", + result.getNameInNamespace()); + } + + public void testLookup_GetNameInNamespace_MultiRdn() { + DirContextAdapter result = (DirContextAdapter) tested + .lookup("cn=Some Person+sn=Person, ou=company1,c=Norway"); + + assertEquals("cn=Some Person+sn=Person, ou=company1, c=Norway", result + .getDn().toString()); + assertEquals( + "cn=Some Person+sn=Person, ou=company1, c=Norway, dc=jayway, dc=se", + result.getNameInNamespace()); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/core/src/itest-openldap/java/org/springframework/ldap/control/LdapTemplatePagedSearchITest.java b/mvn-build/core/src/itest-openldap/java/org/springframework/ldap/control/LdapTemplatePagedSearchITest.java new file mode 100644 index 00000000..61054045 --- /dev/null +++ b/mvn-build/core/src/itest-openldap/java/org/springframework/ldap/control/LdapTemplatePagedSearchITest.java @@ -0,0 +1,157 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.control; + +import java.util.List; + +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; + +import org.springframework.ldap.Person; +import org.springframework.ldap.PersonAttributesMapper; +import org.springframework.ldap.core.AttributesMapperCallbackHandler; +import org.springframework.ldap.core.CollectingNameClassPairCallbackHandler; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.SearchExecutor; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +/** + * Tests the paged search result capability of LdapTemplate. + *

+ * Note: Currently, ApacheDS does not support paged results controls, so this + * test must be run under another directory server, for example OpenLdap. This + * test will not run under ApacheDS, and the other integration tests assume + * ApacheDS and will probably not run under OpenLdap. + * + * @author Ulrik Sandberg + */ +public class LdapTemplatePagedSearchITest extends + AbstractDependencyInjectionSpringContextTests { + + private static final Name BASE = DistinguishedName.EMPTY_PATH; + + private static final String FILTER_STRING = "(&(objectclass=person))"; + + private LdapTemplate tested; + + private CollectingNameClassPairCallbackHandler callbackHandler; + + private SearchControls searchControls; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext-openldap.xml" }; + } + + protected void onSetUp() throws Exception { + super.onSetUp(); + PersonAttributesMapper mapper = new PersonAttributesMapper(); + callbackHandler = new AttributesMapperCallbackHandler(mapper); + searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + } + + protected void onTearDown() throws Exception { + super.onTearDown(); + callbackHandler = null; + tested = null; + searchControls = null; + } + + public void testSearch_PagedResult() { + SearchExecutor searchExecutor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) + throws NamingException { + return ctx.search(BASE, FILTER_STRING, searchControls); + } + }; + Person person; + List list; + PagedResultsCookie cookie; + PagedResultsRequestControl requestControl; + + // Prepare for first search + requestControl = new PagedResultsRequestControl(3); + tested.search(searchExecutor, callbackHandler, requestControl); + cookie = requestControl.getCookie(); + assertNotNull("Cookie should not be null yet", cookie.getCookie()); + list = callbackHandler.getList(); + assertEquals(3, list.size()); + person = (Person) list.get(0); + assertEquals("Some Person", person.getFullname()); + assertEquals("+46 555-123456", person.getPhone()); + person = (Person) list.get(1); + assertEquals("Some Person2", person.getFullname()); + assertEquals("+46 555-654321", person.getPhone()); + person = (Person) list.get(2); + assertEquals("Some Person3", person.getFullname()); + assertEquals("+46 555-123654", person.getPhone()); + + // Prepare for second and last search + requestControl = new PagedResultsRequestControl(3, cookie); + tested.search(searchExecutor, callbackHandler, requestControl); + cookie = requestControl.getCookie(); + assertNull("Cookie should be null now", cookie.getCookie()); + assertEquals(5, list.size()); + person = (Person) list.get(3); + assertEquals("Some Person", person.getFullname()); + assertEquals("+46 555-456321", person.getPhone()); + person = (Person) list.get(4); + assertEquals("Some Person", person.getFullname()); + assertEquals("+45 555-654123", person.getPhone()); + } + + public void testSearch_PagedResult_ConvenienceMethod() { + Person person; + List list; + PagedResultsCookie cookie; + PagedResultsRequestControl requestControl; + + // Prepare for first search + requestControl = new PagedResultsRequestControl(3); + tested.search(BASE, FILTER_STRING, searchControls, + callbackHandler, requestControl); + cookie = requestControl.getCookie(); + assertNotNull("Cookie should not be null yet", cookie.getCookie()); + list = callbackHandler.getList(); + assertEquals(3, list.size()); + person = (Person) list.get(0); + assertEquals("Some Person", person.getFullname()); + person = (Person) list.get(1); + assertEquals("Some Person2", person.getFullname()); + person = (Person) list.get(2); + assertEquals("Some Person3", person.getFullname()); + + // Prepare for second and last search + requestControl = new PagedResultsRequestControl(3, cookie); + tested.search(BASE, FILTER_STRING, searchControls, + callbackHandler, requestControl); + cookie = requestControl.getCookie(); + assertNull("Cookie should be null now", cookie.getCookie()); + assertEquals(5, list.size()); + person = (Person) list.get(3); + assertEquals("Some Person", person.getFullname()); + person = (Person) list.get(4); + assertEquals("Some Person", person.getFullname()); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/core/src/itest-openldap/java/org/springframework/ldap/control/LdapTemplateSortedSearchITest.java b/mvn-build/core/src/itest-openldap/java/org/springframework/ldap/control/LdapTemplateSortedSearchITest.java new file mode 100644 index 00000000..f9f646f9 --- /dev/null +++ b/mvn-build/core/src/itest-openldap/java/org/springframework/ldap/control/LdapTemplateSortedSearchITest.java @@ -0,0 +1,143 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.control; + +import java.util.List; + +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; + +import org.springframework.ldap.Person; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.AttributesMapperCallbackHandler; +import org.springframework.ldap.core.CollectingNameClassPairCallbackHandler; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.SearchExecutor; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +/** + * Tests the still experimental sorted search result capability of LdapTemplate. + * + * @author Ulrik Sandberg + */ +public class LdapTemplateSortedSearchITest extends + AbstractDependencyInjectionSpringContextTests { + + private static final Name BASE = DistinguishedName.EMPTY_PATH; + + private static final String FILTER_STRING = "(&(objectclass=ikeaperson)(cn=gor*))"; + + private LdapTemplate tested; + + private CollectingNameClassPairCallbackHandler callbackHandler; + + private SearchControls searchControls; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext-openldap.xml" }; + } + + protected void onSetUp() throws Exception { + super.onSetUp(); + PersonAttributesMapper mapper = new PersonAttributesMapper(); + callbackHandler = new AttributesMapperCallbackHandler(mapper); + searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + } + + protected void onTearDown() throws Exception { + super.onTearDown(); + callbackHandler = null; + tested = null; + searchControls = null; + } + + public void testSearch_SortControl() { + SearchExecutor searchExecutor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) + throws NamingException { + return ctx.search(BASE, FILTER_STRING, searchControls); + } + }; + SortControlDirContextProcessor requestControl; + + // Prepare for first search + requestControl = new SortControlDirContextProcessor("cn"); + tested.search(searchExecutor, callbackHandler, requestControl); + int resultCode = requestControl.getResultCode(); + boolean sorted = requestControl.isSorted(); + assertTrue("Search result should have been sorted: " + resultCode, sorted); + List list = callbackHandler.getList(); + assertSortedList(list); + } + + public void testSearch_SortControl_ConvenienceMethod() { + SortControlDirContextProcessor requestControl; + + // Prepare for first search + requestControl = new SortControlDirContextProcessor("cn"); + tested.search(BASE, FILTER_STRING, searchControls, callbackHandler, + requestControl); + int resultCode = requestControl.getResultCode(); + boolean sorted = requestControl.isSorted(); + assertTrue("Search result should have been sorted: " + resultCode, sorted); + List list = callbackHandler.getList(); + assertSortedList(list); + } + + private void assertSortedList(List list) { + Person person; + assertEquals(6, list.size()); + person = (Person) list.get(0); + assertEquals("Goran Milenkovic", person.getFullname()); + person = (Person) list.get(1); + assertEquals("Goran Sundberg", person.getFullname()); + person = (Person) list.get(2); + assertEquals("Goran Westerberg", person.getFullname()); + person = (Person) list.get(3); + assertEquals("Gorana Milicevic", person.getFullname()); + person = (Person) list.get(4); + assertEquals("Gordana Canic", person.getFullname()); + person = (Person) list.get(5); + assertEquals("Gordana Russ", person.getFullname()); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } + + private class PersonAttributesMapper implements AttributesMapper { + + /** + * Maps the given attributes into a {@link Person} object. + * + * @see org.springframework.ldap.core.AttributesMapper#mapFromAttributes(javax.naming.directory.Attributes) + */ + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + Person person = new Person(); + person.setFullname((String) attributes.get("cn").get()); + person.setLastname((String) attributes.get("sn").get()); + person.setDescription((String) attributes.get("givenName").get()); + return person; + } + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/AttributeInUseException.java b/mvn-build/core/src/main/java/org/springframework/ldap/AttributeInUseException.java new file mode 100644 index 00000000..d1c89b85 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/AttributeInUseException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI AttributeInUseException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.AttributeInUseException + */ +public class AttributeInUseException extends NamingException { + + public AttributeInUseException( + javax.naming.directory.AttributeInUseException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/AttributeModificationException.java b/mvn-build/core/src/main/java/org/springframework/ldap/AttributeModificationException.java new file mode 100644 index 00000000..f0f7479c --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/AttributeModificationException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI AttributeModificationException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.AttributeModificationException + */ +public class AttributeModificationException extends NamingException { + + public AttributeModificationException( + javax.naming.directory.AttributeModificationException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/AuthenticationException.java b/mvn-build/core/src/main/java/org/springframework/ldap/AuthenticationException.java new file mode 100644 index 00000000..53607134 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/AuthenticationException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI AuthenticationException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.AuthenticationException + */ +public class AuthenticationException extends NamingSecurityException { + + public AuthenticationException(javax.naming.AuthenticationException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/AuthenticationNotSupportedException.java b/mvn-build/core/src/main/java/org/springframework/ldap/AuthenticationNotSupportedException.java new file mode 100644 index 00000000..6036b5fc --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/AuthenticationNotSupportedException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI AuthenticationNotSupportedException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.AuthenticationNotSupportedException + */ +public class AuthenticationNotSupportedException extends + NamingSecurityException { + + public AuthenticationNotSupportedException( + javax.naming.AuthenticationNotSupportedException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/BadLdapGrammarException.java b/mvn-build/core/src/main/java/org/springframework/ldap/BadLdapGrammarException.java new file mode 100644 index 00000000..1fa9d458 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/BadLdapGrammarException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Thrown to indicate that an invalid value has been supplied to an LDAP + * operation. This could be an invalid filter or dn. + * + * @author Mattias Arthursson + */ +public class BadLdapGrammarException extends NamingException { + + private static final long serialVersionUID = 961612585331409470L; + + public BadLdapGrammarException(String message) { + super(message); + } + + public BadLdapGrammarException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/CannotProceedException.java b/mvn-build/core/src/main/java/org/springframework/ldap/CannotProceedException.java new file mode 100644 index 00000000..aba33919 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/CannotProceedException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI CannotProceedException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.CannotProceedException + */ +public class CannotProceedException extends NamingException { + + public CannotProceedException(javax.naming.CannotProceedException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/CommunicationException.java b/mvn-build/core/src/main/java/org/springframework/ldap/CommunicationException.java new file mode 100644 index 00000000..5e6e4b90 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/CommunicationException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI CommunicationException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.CommunicationException + */ +public class CommunicationException extends NamingException { + + public CommunicationException(javax.naming.CommunicationException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/ConfigurationException.java b/mvn-build/core/src/main/java/org/springframework/ldap/ConfigurationException.java new file mode 100644 index 00000000..18da8c1b --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/ConfigurationException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI ConfigurationException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.ConfigurationException + */ +public class ConfigurationException extends NamingException { + + public ConfigurationException(javax.naming.ConfigurationException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/ContextNotEmptyException.java b/mvn-build/core/src/main/java/org/springframework/ldap/ContextNotEmptyException.java new file mode 100644 index 00000000..42de643d --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/ContextNotEmptyException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI ContextNotEmptyException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.ContextNotEmptyException + */ +public class ContextNotEmptyException extends NamingException { + + public ContextNotEmptyException(javax.naming.ContextNotEmptyException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/InsufficientResourcesException.java b/mvn-build/core/src/main/java/org/springframework/ldap/InsufficientResourcesException.java new file mode 100644 index 00000000..0506b867 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/InsufficientResourcesException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InsufficientResourcesException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.InsufficientResourcesException + */ +public class InsufficientResourcesException extends NamingException { + + public InsufficientResourcesException( + javax.naming.InsufficientResourcesException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/InterruptedNamingException.java b/mvn-build/core/src/main/java/org/springframework/ldap/InterruptedNamingException.java new file mode 100644 index 00000000..6cfa8b67 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/InterruptedNamingException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InterruptedNamingException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.InterruptedNamingException + */ +public class InterruptedNamingException extends NamingException { + + public InterruptedNamingException( + javax.naming.InterruptedNamingException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributeIdentifierException.java b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributeIdentifierException.java new file mode 100644 index 00000000..7078fca1 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributeIdentifierException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidAttributeIdentifierException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.InvalidAttributeIdentifierException + */ +public class InvalidAttributeIdentifierException extends NamingException { + + public InvalidAttributeIdentifierException( + javax.naming.directory.InvalidAttributeIdentifierException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributeValueException.java b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributeValueException.java new file mode 100644 index 00000000..9bc5ae0e --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributeValueException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidAttributeValueException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.InvalidAttributeValueException + */ +public class InvalidAttributeValueException extends NamingException { + + public InvalidAttributeValueException( + javax.naming.directory.InvalidAttributeValueException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributesException.java b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributesException.java new file mode 100644 index 00000000..92fc758a --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidAttributesException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidAttributesException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.InvalidAttributesException + */ +public class InvalidAttributesException extends NamingException { + + public InvalidAttributesException( + javax.naming.directory.InvalidAttributesException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/InvalidNameException.java b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidNameException.java new file mode 100644 index 00000000..8999d0cf --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidNameException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidNameException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.InvalidNameException + */ +public class InvalidNameException extends NamingException { + + public InvalidNameException(javax.naming.InvalidNameException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/InvalidSearchControlsException.java b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidSearchControlsException.java new file mode 100644 index 00000000..b987ee5d --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidSearchControlsException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidSearchControlsException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.InvalidSearchControlsException + */ +public class InvalidSearchControlsException extends NamingException { + + public InvalidSearchControlsException( + javax.naming.directory.InvalidSearchControlsException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/InvalidSearchFilterException.java b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidSearchFilterException.java new file mode 100644 index 00000000..b6ac1965 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/InvalidSearchFilterException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI InvalidSearchFilterException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.InvalidSearchFilterException + */ +public class InvalidSearchFilterException extends NamingException { + + public InvalidSearchFilterException( + javax.naming.directory.InvalidSearchFilterException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/LdapReferralException.java b/mvn-build/core/src/main/java/org/springframework/ldap/LdapReferralException.java new file mode 100644 index 00000000..bb072930 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/LdapReferralException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI LdapReferralException. + * + * This class is not abstract. We need to be able to instantiate it, should the + * caught exception be a provider-specific subclass of + * {@link javax.naming.ldap.LdapReferralException}. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.ldap.LdapReferralException + */ +public class LdapReferralException extends ReferralException { + + public LdapReferralException(javax.naming.ldap.LdapReferralException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/LimitExceededException.java b/mvn-build/core/src/main/java/org/springframework/ldap/LimitExceededException.java new file mode 100644 index 00000000..d89d8233 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/LimitExceededException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI LimitExceededException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.LimitExceededException + */ +public class LimitExceededException extends NamingException { + + public LimitExceededException(javax.naming.LimitExceededException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/LinkException.java b/mvn-build/core/src/main/java/org/springframework/ldap/LinkException.java new file mode 100644 index 00000000..ee85ad55 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/LinkException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI LinkException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.LinkException + */ +public class LinkException extends NamingException { + + public LinkException(javax.naming.LinkException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/LinkLoopException.java b/mvn-build/core/src/main/java/org/springframework/ldap/LinkLoopException.java new file mode 100644 index 00000000..00e5184c --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/LinkLoopException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI LinkLoopException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.LinkLoopException + */ +public class LinkLoopException extends LinkException { + + public LinkLoopException(javax.naming.LinkLoopException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/MalformedLinkException.java b/mvn-build/core/src/main/java/org/springframework/ldap/MalformedLinkException.java new file mode 100644 index 00000000..665e4ebc --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/MalformedLinkException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI MalformedLinkException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.MalformedLinkException + */ +public class MalformedLinkException extends LinkException { + + public MalformedLinkException(javax.naming.MalformedLinkException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/NameAlreadyBoundException.java b/mvn-build/core/src/main/java/org/springframework/ldap/NameAlreadyBoundException.java new file mode 100644 index 00000000..25c2f8e2 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/NameAlreadyBoundException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NameAlreadyBoundException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NameAlreadyBoundException + */ +public class NameAlreadyBoundException extends NamingException { + + public NameAlreadyBoundException( + javax.naming.NameAlreadyBoundException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/NameNotFoundException.java b/mvn-build/core/src/main/java/org/springframework/ldap/NameNotFoundException.java new file mode 100644 index 00000000..ada8db7a --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/NameNotFoundException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NameNotFoundException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NameNotFoundException + */ +public class NameNotFoundException extends NamingException { + + public NameNotFoundException(String msg) { + super(msg); + } + + public NameNotFoundException(javax.naming.NameNotFoundException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/NamingException.java b/mvn-build/core/src/main/java/org/springframework/ldap/NamingException.java new file mode 100644 index 00000000..5bf22c1e --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/NamingException.java @@ -0,0 +1,186 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import javax.naming.Name; + +import org.springframework.core.NestedRuntimeException; + +/** + * Base class for exception thrown by the framework whenever it encounters a + * problem related to LDAP. + * + * @author Ulrik Sandberg + * @since 1.2 + */ +public abstract class NamingException extends NestedRuntimeException { + + private Throwable cause; + + /** + * Overrides {@link NestedRuntimeException#getCause()} since serialization + * always tries to serialize the base class before the subclass. Our + * cause may have a resolvedObj that is not + * serializable. By storing the cause in this class, we get a chance at + * temporarily nulling the cause before serialization, thus in effect making + * the current instance serializable. + */ + public Throwable getCause() { + // Even if you cannot set the cause of this exception other than through + // the constructor, we check for the cause being "this" here, as the cause + // could still be set to "this" via reflection: for example, by a remoting + // deserializer like Hessian's. + return (this.cause == this ? null : this.cause); + } + + /** + * Constructor that takes a message. + * + * @param msg + * the detail message + */ + public NamingException(String msg) { + super(msg); + } + + /** + * Constructor that allows a message and a root cause. + * + * @param msg + * the detail message + * @param cause + * the cause of the exception. This argument is generally + * expected to be a proper subclass of + * {@link javax.naming.NamingException}. + */ + public NamingException(String msg, Throwable cause) { + super(msg); + this.cause = cause; + } + + /** + * Constructor that allows a plain root cause, intended for subclasses + * mirroring corresponding javax.naming exceptions. + * + * @param cause + * the cause of the exception. This argument is generally + * expected to be a proper subclass of + * {@link javax.naming.NamingException}. + */ + public NamingException(Throwable cause) { + this(cause != null ? cause.getMessage() : null, cause); + } + + /** + * Convenience method to get the explanation associated with this exception, + * if the root cause was an instance of {@link javax.naming.NamingException}. + * + * @return a detail string explaining more about this exception if the root + * cause is an instance of javax.naming.NamingException, or + * null if there is no detail message for this + * exception + */ + public String getExplanation() { + if (getCause() instanceof javax.naming.NamingException) { + return ((javax.naming.NamingException) getCause()).getExplanation(); + } + return null; + } + + /** + * Convenience method to get the unresolved part of the name associated with + * this exception, if the root cause was an instance of + * {@link javax.naming.NamingException}. + * + * @return a composite name describing the part of the name that has not + * been resolved if the root cause is an instance of + * javax.naming.NamingException, or null if the + * remaining name field has not been set + */ + public Name getRemainingName() { + if (getCause() instanceof javax.naming.NamingException) { + return ((javax.naming.NamingException) getCause()) + .getRemainingName(); + } + return null; + } + + /** + * Convenience method to get the leading portion of the resolved name + * associated with this exception, if the root cause was an instance of + * {@link javax.naming.NamingException}. + * + * @return a composite name describing the the leading portion of the name + * that was resolved successfully if the root cause is an instance + * of javax.naming.NamingException, or null if the + * resolved name field has not been set + */ + public Name getResolvedName() { + if (getCause() instanceof javax.naming.NamingException) { + return ((javax.naming.NamingException) getCause()) + .getResolvedName(); + } + return null; + } + + /** + * Convenience method to get the resolved object associated with this + * exception, if the root cause was an instance of + * {@link javax.naming.NamingException}. + * + * @return the object that was resolved so far if the root cause is an + * instance of javax.naming.NamingException, or null + * if the resolved object field has not been set + */ + public Object getResolvedObj() { + if (getCause() instanceof javax.naming.NamingException) { + return ((javax.naming.NamingException) getCause()).getResolvedObj(); + } + return null; + } + + /** + * Checks if the resolvedObj of the causing exception is + * suspected to be non-serializable, and if so temporarily nulls it before + * calling the default serialization mechanism. + * + * @param stream + * the stream onto which this object is serialized + * @throws IOException + * if there is an error writing this object to the stream + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + Object resolvedObj = getResolvedObj(); + boolean serializable = resolvedObj instanceof Serializable; + if (resolvedObj != null && !serializable) { + // the cause is of this type, since resolvedObj is not null + javax.naming.NamingException namingException = (javax.naming.NamingException) getCause(); + namingException.setResolvedObj(null); + try { + stream.defaultWriteObject(); + } finally { + namingException.setResolvedObj(resolvedObj); + } + } else { + stream.defaultWriteObject(); + } + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/NamingSecurityException.java b/mvn-build/core/src/main/java/org/springframework/ldap/NamingSecurityException.java new file mode 100644 index 00000000..6ae15225 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/NamingSecurityException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NamingSecurityException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NamingSecurityException + */ +public abstract class NamingSecurityException extends NamingException { + + public NamingSecurityException(javax.naming.NamingSecurityException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/NoInitialContextException.java b/mvn-build/core/src/main/java/org/springframework/ldap/NoInitialContextException.java new file mode 100644 index 00000000..f54d5eee --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/NoInitialContextException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NoInitialContextException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NoInitialContextException + */ +public class NoInitialContextException extends NamingException { + + public NoInitialContextException( + javax.naming.NoInitialContextException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/NoPermissionException.java b/mvn-build/core/src/main/java/org/springframework/ldap/NoPermissionException.java new file mode 100644 index 00000000..fce493fe --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/NoPermissionException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NoPermissionException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NoPermissionException + */ +public class NoPermissionException extends NamingSecurityException { + + public NoPermissionException(javax.naming.NoPermissionException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/NoSuchAttributeException.java b/mvn-build/core/src/main/java/org/springframework/ldap/NoSuchAttributeException.java new file mode 100644 index 00000000..b7739bb9 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/NoSuchAttributeException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NoSuchAttributeException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.NoSuchAttributeException + */ +public class NoSuchAttributeException extends NamingException { + + public NoSuchAttributeException( + javax.naming.directory.NoSuchAttributeException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/NotContextException.java b/mvn-build/core/src/main/java/org/springframework/ldap/NotContextException.java new file mode 100644 index 00000000..5a3a6658 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/NotContextException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI NotContextException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.NotContextException + */ +public class NotContextException extends NamingException { + + public NotContextException(javax.naming.NotContextException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/OperationNotSupportedException.java b/mvn-build/core/src/main/java/org/springframework/ldap/OperationNotSupportedException.java new file mode 100644 index 00000000..7613d1a3 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/OperationNotSupportedException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI OperationNotSupportedException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.OperationNotSupportedException + */ +public class OperationNotSupportedException extends NamingException { + + public OperationNotSupportedException( + javax.naming.OperationNotSupportedException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/PartialResultException.java b/mvn-build/core/src/main/java/org/springframework/ldap/PartialResultException.java new file mode 100644 index 00000000..552a1d40 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/PartialResultException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI PartialResultException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.PartialResultException + */ +public class PartialResultException extends NamingException { + + public PartialResultException(javax.naming.PartialResultException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/ReferralException.java b/mvn-build/core/src/main/java/org/springframework/ldap/ReferralException.java new file mode 100644 index 00000000..3be8bf47 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/ReferralException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI ReferralException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.ReferralException + */ +public abstract class ReferralException extends NamingException { + + public ReferralException(javax.naming.ReferralException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/SchemaViolationException.java b/mvn-build/core/src/main/java/org/springframework/ldap/SchemaViolationException.java new file mode 100644 index 00000000..e9294aa4 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/SchemaViolationException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI SchemaViolationException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.directory.SchemaViolationException + */ +public class SchemaViolationException extends NamingException { + + public SchemaViolationException( + javax.naming.directory.SchemaViolationException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/ServiceUnavailableException.java b/mvn-build/core/src/main/java/org/springframework/ldap/ServiceUnavailableException.java new file mode 100644 index 00000000..1e8c09a9 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/ServiceUnavailableException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI ServiceUnavailableException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.ServiceUnavailableException + */ +public class ServiceUnavailableException extends NamingException { + + public ServiceUnavailableException( + javax.naming.ServiceUnavailableException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/SizeLimitExceededException.java b/mvn-build/core/src/main/java/org/springframework/ldap/SizeLimitExceededException.java new file mode 100644 index 00000000..52b9a99f --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/SizeLimitExceededException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI SizeLimitExceededException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.SizeLimitExceededException + */ +public class SizeLimitExceededException extends LimitExceededException { + + public SizeLimitExceededException( + javax.naming.SizeLimitExceededException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/TimeLimitExceededException.java b/mvn-build/core/src/main/java/org/springframework/ldap/TimeLimitExceededException.java new file mode 100644 index 00000000..42d55d89 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/TimeLimitExceededException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Runtime exception mirroring the JNDI TimeLimitExceededException. + * + * @author Ulrik Sandberg + * @since 1.2 + * @see javax.naming.TimeLimitExceededException + */ +public class TimeLimitExceededException extends LimitExceededException { + + public TimeLimitExceededException( + javax.naming.TimeLimitExceededException cause) { + super(cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/UncategorizedLdapException.java b/mvn-build/core/src/main/java/org/springframework/ldap/UncategorizedLdapException.java new file mode 100644 index 00000000..47c8fd72 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/UncategorizedLdapException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * NamingException to be thrown when no other matching subclass is found. + * + * @author Ulrik Sandberg + * @since 1.2 + */ +public class UncategorizedLdapException extends NamingException { + + public UncategorizedLdapException(String msg) { + super(msg); + } + + public UncategorizedLdapException(String msg, Throwable cause) { + super(msg, cause); + } + + public UncategorizedLdapException(Throwable cause) { + super("Uncategorized exception occured during LDAP processing", cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/authentication/AcegiAuthenticationSource.java b/mvn-build/core/src/main/java/org/springframework/ldap/authentication/AcegiAuthenticationSource.java new file mode 100644 index 00000000..2a8fa91e --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/authentication/AcegiAuthenticationSource.java @@ -0,0 +1,87 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.authentication; + +import org.acegisecurity.Authentication; +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.core.AuthenticationSource; + +/** + * An AuthenticationSource to retrieve authentication information stored in + * Acegi's SecurityContextHolder. Use Acegi's LdapAuthenticationProvider have a + * LdapUserDetails object placed in the authentication. + * + * @author Mattias Arthursson + * + */ +public class AcegiAuthenticationSource implements AuthenticationSource { + private static final Log log = LogFactory + .getLog(AcegiAuthenticationSource.class); + + /** + * Get the principals of the logged in user, in this case the distinguished + * name. + * + * @return the distinguished name of the logged in user. + */ + public String getPrincipal() { + Authentication authentication = SecurityContextHolder.getContext() + .getAuthentication(); + if (authentication != null) { + Object principal = authentication.getPrincipal(); + if (principal instanceof LdapUserDetails) { + LdapUserDetails details = (LdapUserDetails) principal; + return details.getDn(); + } else if (authentication instanceof AnonymousAuthenticationToken) { + if (log.isDebugEnabled()) { + log + .debug("Anonymous Authentication, returning empty String as Principal"); + } + return ""; + } else { + throw new IllegalArgumentException( + "The principal property of the authentication object -" + + "needs to be a LdapUserDetails."); + } + } else { + log.warn("No Authentication object set in SecurityContext - " + + "returning empty String as Principal"); + return ""; + } + } + + /* + * @see org.springframework.ldap.core.AuthenticationSource#getCredentials() + */ + public String getCredentials() { + Authentication authentication = SecurityContextHolder.getContext() + .getAuthentication(); + + if (authentication != null) { + return (String) authentication.getCredentials(); + } else { + log.warn("No Authentication object set in SecurityContext - " + + "returning empty String as Credentials"); + return ""; + } + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecorator.java b/mvn-build/core/src/main/java/org/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecorator.java new file mode 100644 index 00000000..7f5acc9a --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecorator.java @@ -0,0 +1,157 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.authentication; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.ldap.core.AuthenticationSource; + +/** + * Decorator on AuthenticationSource to have default authentication information + * be returned should the target return empty principal and credentials. Useful + * in combination with {@link AcegiAuthenticationSource} if users are to be + * allowed to read some information even though they are not logged in. + *

+ * Note: The defaultUser should be an non-privileged + * user. This is important as this is the one that will be used when no user is + * logged in (i.e. empty principal is returned from the target + * AuthenticationSource). + * + * @author Mattias Arthursson + * + */ +public class DefaultValuesAuthenticationSourceDecorator implements + AuthenticationSource, InitializingBean { + + private AuthenticationSource target; + + private String defaultUser; + + private String defaultPassword; + + /** + * Constructor for bean usage. + */ + public DefaultValuesAuthenticationSourceDecorator() { + } + + /** + * Constructor to setup instance directly. + * + * @param target + * the target AuthenticationSource. + * @param defaultUser + * dn of the user to use when the target returns an empty + * principal. + * @param defaultPassword + * password of the user to use when the target returns an empty + * principal. + */ + public DefaultValuesAuthenticationSourceDecorator( + AuthenticationSource target, String defaultUser, + String defaultPassword) { + this.target = target; + this.defaultUser = defaultUser; + this.defaultPassword = defaultPassword; + } + + /** + * Checks if the target's principal is not empty; if not, the credentials + * from the target is returned - otherwise return the + * defaultPassword. + * + * @return the target's password if the target's principal is not empty, the + * defaultPassword otherwise. + */ + public String getCredentials() { + if (StringUtils.isNotEmpty(target.getPrincipal())) { + return target.getCredentials(); + } else { + return defaultPassword; + } + } + + /** + * Checks if the target's principal is not empty; if not, this is returned - + * otherwise return the defaultPassword. + * + * @return the target's principal if it is not empty, the + * defaultPassword otherwise. + */ + public String getPrincipal() { + String principal = target.getPrincipal(); + if (StringUtils.isNotEmpty(principal)) { + return principal; + } else { + return defaultUser; + } + } + + /** + * Set the password of the default user. + * + * @param defaultPassword + * the password of the default user. + */ + public void setDefaultPassword(String defaultPassword) { + this.defaultPassword = defaultPassword; + } + + /** + * Set the default user DN. This should be a non-privileged user, since it + * will be used when no authentication information is returned from the + * target. + * + * @param defaultUser + * DN of the default user. + */ + public void setDefaultUser(String defaultUser) { + this.defaultUser = defaultUser; + } + + /** + * Set the target AuthenticationSource. + * + * @param target + * the target AuthenticationSource. + */ + public void setTarget(AuthenticationSource target) { + this.target = target; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + if (target == null) { + throw new IllegalArgumentException( + "Property 'target' must be set.'"); + } + + if (defaultUser == null) { + throw new IllegalArgumentException( + "Property 'defaultUser' must be set.'"); + } + + if (defaultPassword == null) { + throw new IllegalArgumentException( + "Property 'defaultPassword' must be set.'"); + } + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/authentication/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/authentication/package.html new file mode 100644 index 00000000..4e314da9 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/authentication/package.html @@ -0,0 +1,7 @@ + + + +Support classes for custom authentication. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/control/AbstractRequestControlDirContextProcessor.java b/mvn-build/core/src/main/java/org/springframework/ldap/control/AbstractRequestControlDirContextProcessor.java new file mode 100644 index 00000000..5ecdee02 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/control/AbstractRequestControlDirContextProcessor.java @@ -0,0 +1,93 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.control; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.core.DirContextProcessor; + +/** + * Abstract superclass with responsibility to apply a single RequestControl on + * an LdapContext, preserving any existing controls. Subclasses should implement + * {@link DirContextProcessor#postProcess(DirContext)} and template method + * {@link #createRequestControl()}. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public abstract class AbstractRequestControlDirContextProcessor implements + DirContextProcessor { + protected Log log = LogFactory + .getLog(AbstractRequestControlDirContextProcessor.class); + + /** + * Get the existing RequestControls from the LdapContext, call + * {@link #createRequestControl()} to get a new instance, build a new array + * of Controls and set it on the LdapContext. + *

+ * The {@link Control} feature is specific for LDAP v3 and thus applies only + * to {@link LdapContext}. However, the generic DirContextProcessor + * mechanism used for calling preProcess and + * postProcess uses DirContext, since it also works for LDAP + * v2. This is the reason that DirContext has to be cast to a LdapContext. + * + * @param ctx + * an LdapContext instance. + * @throws NamingException + * @throws IllegalArgumentException + * if the supplied DirContext is not an LdapContext. + */ + public void preProcess(DirContext ctx) throws NamingException { + LdapContext ldapContext; + if (ctx instanceof LdapContext) { + ldapContext = (LdapContext) ctx; + } else { + throw new IllegalArgumentException( + "Request Control operations require LDAPv3 - " + + "Context must be of type LdapContext"); + } + + Control[] requestControls = ldapContext.getRequestControls(); + if (requestControls == null) { + requestControls = new Control[0]; + } + Control newControl = createRequestControl(); + + Control[] newControls = new Control[requestControls.length + 1]; + for (int i = 0; i < requestControls.length; i++) { + newControls[i] = requestControls[i]; + } + + // Add the new Control at the end of the array. + newControls[newControls.length - 1] = newControl; + + ldapContext.setRequestControls(newControls); + } + + /** + * Create an instance of the appropriate RequestControl. + * + * @return the new instance. + */ + public abstract Control createRequestControl(); + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/control/CreateControlFailedException.java b/mvn-build/core/src/main/java/org/springframework/ldap/control/CreateControlFailedException.java new file mode 100644 index 00000000..3a026645 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/control/CreateControlFailedException.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.control; + +import org.springframework.ldap.NamingException; + +/** + * Thrown by an AbstractRequestControlDirContextProcessor when it cannot create + * a request control. + * + * @author Ulrik Sandberg + * @since 1.2 + */ +public class CreateControlFailedException extends NamingException { + + /** + * Create a new CreateControlFailedException. + * + * @param msg + * the detail message + */ + public CreateControlFailedException(String msg) { + super(msg); + } + + /** + * Create a new CreateControlFailedException. + * + * @param msg + * the detail message + * @param cause + * the root cause (if any) + */ + public CreateControlFailedException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResult.java b/mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResult.java new file mode 100644 index 00000000..d2e898af --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResult.java @@ -0,0 +1,92 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.control; + +import java.util.List; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +/** + * Bean to encapsulate a result List and a {@link PagedResultsCookie} to use for + * returning the results when using {@link PagedResultsRequestControl}. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public class PagedResult { + + private List resultList; + + private PagedResultsCookie cookie; + + /** + * Constructs a PagedResults using the supplied List and + * {@link PagedResultsCookie}. + * + * @param resultList + * the result list. + * @param cookie + * the cookie. + */ + public PagedResult(List resultList, PagedResultsCookie cookie) { + this.resultList = resultList; + this.cookie = cookie; + } + + /** + * Get the cookie. + * + * @return the cookie. + */ + public PagedResultsCookie getCookie() { + return cookie; + } + + /** + * Get the result list. + * + * @return the result list. + */ + public List getResultList() { + return resultList; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj != null && this.getClass().equals(obj.getClass())) { + PagedResult that = (PagedResult) obj; + return new EqualsBuilder().append(this.resultList, that.resultList) + .append(this.cookie, that.cookie).isEquals(); + } + + return false; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return new HashCodeBuilder().append(this.resultList) + .append(this.cookie).toHashCode(); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResultsCookie.java b/mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResultsCookie.java new file mode 100644 index 00000000..1a3e9385 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResultsCookie.java @@ -0,0 +1,78 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.control; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +import com.sun.jndi.ldap.ctl.PagedResultsControl; + +/** + * Wrapper class for the cookie returned when using the + * {@link PagedResultsControl}. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public class PagedResultsCookie { + + private byte[] cookie; + + /** + * Constructor. + * + * @param cookie + * the cookie returned by a PagedResultsResponseControl. + */ + public PagedResultsCookie(byte[] cookie) { + this.cookie = ArrayUtils.clone(cookie); + } + + /** + * Get the cookie. + * + * @return the cookie. + */ + public byte[] getCookie() { + return ArrayUtils.clone(cookie); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj != null && this.getClass().equals(obj.getClass())) { + PagedResultsCookie that = (PagedResultsCookie) obj; + return new EqualsBuilder().append(this.cookie, that.cookie) + .isEquals(); + } + + return false; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return new HashCodeBuilder().append(this.cookie).toHashCode(); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResultsRequestControl.java b/mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResultsRequestControl.java new file mode 100644 index 00000000..dcc9dc6c --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/control/PagedResultsRequestControl.java @@ -0,0 +1,238 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.control; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; + +import org.springframework.util.ReflectionUtils; + +import com.sun.jndi.ldap.ctl.PagedResultsControl; +import com.sun.jndi.ldap.ctl.PagedResultsResponseControl; + +/** + * DirContextProcessor implementation for managing the paged results control. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public class PagedResultsRequestControl extends + AbstractRequestControlDirContextProcessor { + + private static final Class DEFAULT_RESPONSE_CONTROL = PagedResultsResponseControl.class; + + private static final boolean CRITICAL_CONTROL = true; + + private static final String JAVA5_RESPONSE_CONTROL = "javax.naming.ldap.PagedResultsResponseControl"; + + private int pageSize; + + private PagedResultsCookie cookie; + + private int resultSize; + + private Class responseControlClass = DEFAULT_RESPONSE_CONTROL; + + private Class fallbackResponseControlClass; + + private Class currentResponseControlClass; + + /** + * Constructs a new instance. This constructor should be used when + * performing the first paged search operation, when no other results have + * been retrieved. + * + * @param pageSize + * the page size. + */ + public PagedResultsRequestControl(int pageSize) { + this(pageSize, null); + } + + /** + * Constructs a new instance with the supplied page size and cookie. The + * cookie must be the exact same instance as received from a previous paged + * resullts search, or null if it is the first in an + * operation sequence. + * + * @param pageSize + * the page size. + * @param cookie + * the cookie, as received from a previous search. + */ + public PagedResultsRequestControl(int pageSize, PagedResultsCookie cookie) { + this.pageSize = pageSize; + this.cookie = cookie; + fallbackResponseControlClass = loadFallbackResponseControlClass(); + } + + /** + * Get the cookie. + * + * @return the cookie. + */ + public PagedResultsCookie getCookie() { + return cookie; + } + + /** + * Get the page size. + * + * @return the page size. + */ + public int getPageSize() { + return pageSize; + } + + /** + * Get the total estimated number of entries that matches the issued search. + * Note that this value is optional for the LDAP server to return, so it + * does not always contain any valid data. + * + * @return the estimated result size, if returned from the server. + */ + public int getResultSize() { + return resultSize; + } + + /** + * Set the class of the expected ResponseControl for the paged results + * response. The default is {@link PagedResultsResponseControl}. + * + * @param responseControlClass + * Class of the expected response control. + */ + public void setResponseControlClass(Class responseControlClass) { + this.responseControlClass = responseControlClass; + } + + /* + * @see org.springframework.ldap.control.AbstractRequestControlDirContextProcessor#createRequestControl() + */ + public Control createRequestControl() { + try { + if (cookie != null) { + return new PagedResultsControl(pageSize, cookie.getCookie(), + CRITICAL_CONTROL); + } else { + return new PagedResultsControl(pageSize); + } + } catch (IOException e) { + throw new CreateControlFailedException( + "Error creating PagedResultsControl", e); + } + } + + /* + * @see org.springframework.ldap.core.DirContextProcessor#postProcess(javax.naming.directory.DirContext) + */ + public void postProcess(DirContext ctx) throws NamingException { + // initialize from property + currentResponseControlClass = responseControlClass; + + LdapContext ldapContext = (LdapContext) ctx; + Control[] responseControls = ldapContext.getResponseControls(); + if (responseControls == null) { + responseControls = new Control[0]; + } + + // Go through response controls and get info, regardless of class + for (int i = 0; i < responseControls.length; i++) { + Control responseControl = responseControls[i]; + + // check for match, try fallback otherwise + if (isPagedResultsResponseControl(responseControl)) { + Object control = responseControl; + byte[] result = (byte[]) invokeMethod("getCookie", + currentResponseControlClass, control); + this.cookie = new PagedResultsCookie(result); + Integer wrapper = (Integer) invokeMethod("getResultSize", + currentResponseControlClass, control); + this.resultSize = wrapper.intValue(); + } + } + } + + /** + * Check if the given control matches a paged results response control. Try + * the fallback class from Java5 if there is no match. Set the + * {@link #currentResponseControlClass} to the fallback if it matches. + * + * @param responseControl + * the control to check for a match + * @return whether the control is a paged results response control + */ + private boolean isPagedResultsResponseControl(Control responseControl) { + if (responseControl.getClass().isAssignableFrom( + currentResponseControlClass)) { + return true; + } + if (fallbackResponseControlClass != null + && responseControl.getClass().isAssignableFrom( + fallbackResponseControlClass)) { + currentResponseControlClass = fallbackResponseControlClass; + return true; + } + return false; + } + + private Class loadFallbackResponseControlClass() { + Class fallbackResponseControlClass = null; + try { + fallbackResponseControlClass = Class + .forName(JAVA5_RESPONSE_CONTROL); + } catch (ClassNotFoundException e) { + log.debug("Could not load Java5 response control class " + + JAVA5_RESPONSE_CONTROL); + } + return fallbackResponseControlClass; + } + + private Object invokeMethod(String method, Class clazz, Object control) { + // For Spring 2.0 ReflectionUtils could be used for all of this, but + // since we still want to support the 1.2 branch we do it manually and + // only use the stuff present in 1.2.8. + Method actualMethod = null; + Object retval = null; + try { + actualMethod = clazz.getMethod(method, new Class[0]); + } catch (SecurityException e) { + ReflectionUtils.handleReflectionException(e); + } catch (NoSuchMethodException e) { + ReflectionUtils.handleReflectionException(e); + } + + try { + retval = actualMethod.invoke(control, new Object[0]); + } catch (IllegalArgumentException e) { + ReflectionUtils.handleReflectionException(e); + } catch (IllegalAccessException e) { + ReflectionUtils.handleReflectionException(e); + } catch (InvocationTargetException e) { + ReflectionUtils.handleReflectionException(e); + } + + // Retval will be set unless an exception has been thrown. + return retval; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java b/mvn-build/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java new file mode 100644 index 00000000..94c6f92a --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java @@ -0,0 +1,247 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.control; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; + +import org.springframework.ldap.control.AbstractRequestControlDirContextProcessor; +import org.springframework.ldap.control.CreateControlFailedException; +import org.springframework.util.ReflectionUtils; + +import com.sun.jndi.ldap.ctl.SortControl; +import com.sun.jndi.ldap.ctl.SortResponseControl; + +/** + * DirContextProcessor implementation for managing the {@link SortControl}. + * Note that this class is stateful, so a new instance needs to be instantiated + * for each new search. + * + * @author Ulrik Sandberg + */ +public class SortControlDirContextProcessor extends + AbstractRequestControlDirContextProcessor { + + private static final Class DEFAULT_RESPONSE_CONTROL = SortResponseControl.class; + + private static final boolean CRITICAL_CONTROL = true; + + private static final String JAVA5_RESPONSE_CONTROL = "javax.naming.ldap.SortResponseControl"; + + /** + * What key to sort on. + */ + private String sortKey; + + /** + * Whether the search result actually was sorted. + */ + private boolean sorted; + + /** + * The result code of the supposedly sorted search. + */ + private int resultCode; + + private Class responseControlClass = DEFAULT_RESPONSE_CONTROL; + + private Class fallbackResponseControlClass; + + private Class currentResponseControlClass; + + /** + * Constructs a new instance using the supplied sort key. + * + * @param sortKey + * the sort key, i.e. the attribute name to sort on. + */ + public SortControlDirContextProcessor(String sortKey) { + this.sortKey = sortKey; + fallbackResponseControlClass = loadFallbackResponseControlClass(); + setSorted(false); + setResultCode(-1); + } + + /** + * Set the class of the expected ResponseControl for the sorted result + * response. The default is {@link SortResponseControl}. + * + * @param responseControlClass + * Class of the expected response control. + */ + public void setResponseControlClass(Class responseControlClass) { + this.responseControlClass = responseControlClass; + } + + /** + * Check whether the returned values were actually sorted by the server. + * + * @return true if the result was sorted, false + * otherwise. + */ + public boolean isSorted() { + return sorted; + } + + private void setSorted(boolean sorted) { + this.sorted = sorted; + } + + /** + * Get the result code returned by the control. + * + * @return result code. + */ + public int getResultCode() { + return resultCode; + } + + private void setResultCode(int sortResult) { + this.resultCode = sortResult; + } + + /** + * Get the sort key. + * + * @return the sort key. + */ + public String getSortKey() { + return sortKey; + } + + /** + * Set the sort key, i.e. the attribute on which to sort on. + * + * @param sortKey + * the sort key. + */ + public void setSortKey(String sortKey) { + this.sortKey = sortKey; + } + + /* + * @see org.springframework.ldap.control.AbstractRequestControlDirContextProcessor#createRequestControl() + */ + public Control createRequestControl() { + try { + return new SortControl(new String[] { sortKey }, CRITICAL_CONTROL); + } catch (IOException e) { + throw new CreateControlFailedException( + "Error creating SortControl", e); + } + } + + /* + * @see org.springframework.ldap.core.DirContextProcessor#postProcess(javax.naming.directory.DirContext) + */ + public void postProcess(DirContext ctx) throws NamingException { + // initialize from property + currentResponseControlClass = responseControlClass; + + LdapContext ldapContext = (LdapContext) ctx; + Control[] responseControls = ldapContext.getResponseControls(); + + if (responseControls == null) { + return; + } + + // Go through response controls and get info, regardless of class + for (int i = 0; i < responseControls.length; i++) { + Control responseControl = responseControls[i]; + + // check for match, try fallback otherwise + if (isSortResponseControl(responseControl)) { + Object control = responseControl; + Boolean result = (Boolean) invokeMethod("isSorted", + currentResponseControlClass, control); + setSorted(result.booleanValue()); + Integer code = (Integer) invokeMethod("getResultCode", + currentResponseControlClass, control); + setResultCode(code.intValue()); + } + } + } + + /** + * Check if the given control matches a sort response control. Try the + * fallback class from Java5 if there is no match. Set the + * {@link #currentResponseControlClass} to the fallback if it matches. + * + * @param responseControl + * the control to check for a match + * @return whether the control is a paged results response control + */ + private boolean isSortResponseControl(Control responseControl) { + if (responseControl.getClass().isAssignableFrom( + currentResponseControlClass)) { + return true; + } + if (fallbackResponseControlClass != null + && responseControl.getClass().isAssignableFrom( + fallbackResponseControlClass)) { + currentResponseControlClass = fallbackResponseControlClass; + return true; + } + return false; + } + + private Class loadFallbackResponseControlClass() { + Class fallbackResponseControlClass = null; + try { + fallbackResponseControlClass = Class + .forName(JAVA5_RESPONSE_CONTROL); + } catch (ClassNotFoundException e) { + log.debug("Could not load Java5 response control class " + + JAVA5_RESPONSE_CONTROL); + } + return fallbackResponseControlClass; + } + + private Object invokeMethod(String method, Class clazz, Object control) { + // For Spring 2.0 ReflectionUtils could be used for all of this, but + // since we still want to support the 1.2 branch we do it manually and + // only use the stuff present in 1.2.8. + Method actualMethod = null; + Object retval = null; + try { + actualMethod = clazz.getMethod(method, new Class[0]); + } catch (SecurityException e) { + ReflectionUtils.handleReflectionException(e); + } catch (NoSuchMethodException e) { + ReflectionUtils.handleReflectionException(e); + } + + try { + retval = actualMethod.invoke(control, new Object[0]); + } catch (IllegalArgumentException e) { + ReflectionUtils.handleReflectionException(e); + } catch (IllegalAccessException e) { + ReflectionUtils.handleReflectionException(e); + } catch (InvocationTargetException e) { + ReflectionUtils.handleReflectionException(e); + } + + // Retval will be set unless an exception has been thrown. + return retval; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/control/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/control/package.html new file mode 100644 index 00000000..af3c0892 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/control/package.html @@ -0,0 +1,7 @@ + + + +Support classes for custom request control context processors. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/AttributeModificationsAware.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/AttributeModificationsAware.java new file mode 100644 index 00000000..089ca336 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/AttributeModificationsAware.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.directory.ModificationItem; + +/** + * Indicates that the implementing class is capable of keeping track of any + * attribute modifications and return them as ModificationItems. + * + * @author Mattias Arthursson + * + */ +public interface AttributeModificationsAware { + + /** + * Creates an array of which attributes have been changed, added or removed + * since the initialization of this object. + * + * @return an array of modification items. + */ + public ModificationItem[] getModificationItems(); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/AttributesMapper.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/AttributesMapper.java new file mode 100644 index 00000000..b090bf1f --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/AttributesMapper.java @@ -0,0 +1,55 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +/** + * An interface used by LdapTemplate for mapping LDAP Attributes to beans. + * Implementions of this interface perform the actual work of extracting + * results, but need not worry about exception handling. NamingExceptions will + * be caught and handled correctly by the {@link LdapTemplate} class. + *

+ * Typically used in search methods of {@link LdapTemplate}. + * AttributeMapper objects are normally stateless and thus + * reusable; they are ideal for implementing attribute-mapping logic in one + * place. + *

+ * Alternatively, consider using a {@link ContextMapper} in stead. + * + * @see LdapTemplate#search(Name, String, AttributesMapper) + * @see LdapTemplate#lookup(Name, AttributesMapper) + * @see ContextMapper + * + * @author Mattias Arthursson + */ +public interface AttributesMapper { + /** + * Map Attributes to an object. The supplied attributes are the attributes + * from a single SearchResult. + * + * @param attributes + * attributes from a SearchResult. + * @return an object built from the attributes. + * @throws NamingException + * if any error occurs mapping the attributes + */ + public Object mapFromAttributes(Attributes attributes) + throws NamingException; +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/AttributesMapperCallbackHandler.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/AttributesMapperCallbackHandler.java new file mode 100644 index 00000000..dfbdd552 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/AttributesMapperCallbackHandler.java @@ -0,0 +1,66 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.NameClassPair; +import javax.naming.directory.Attributes; +import javax.naming.directory.SearchResult; + +import org.springframework.ldap.support.LdapUtils; + +/** + * A CollectingNameClassPairCallbackHandler to wrap an {@link AttributesMapper}. + * That is, the found object is extracted from the {@link Attributes} of each + * {@link SearchResult}, and then passed to the specified + * {@link AttributesMapper} for translation. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + * @since 1.2 + */ +public class AttributesMapperCallbackHandler extends + CollectingNameClassPairCallbackHandler { + private AttributesMapper mapper; + + /** + * Constructs a new instance around the specified {@link AttributesMapper}. + * + * @param mapper + * the target mapper. + */ + public AttributesMapperCallbackHandler(AttributesMapper mapper) { + this.mapper = mapper; + } + + /** + * Cast the NameClassPair to a SearchResult and pass its attributes to the + * {@link AttributesMapper}. + * + * @param nameClassPair + * a SearchResult instance. + * @return the Object returned from the mapper. + */ + public Object getObjectFromNameClassPair(NameClassPair nameClassPair) { + SearchResult searchResult = (SearchResult) nameClassPair; + Attributes attributes = searchResult.getAttributes(); + try { + return mapper.mapFromAttributes(attributes); + } catch (javax.naming.NamingException e) { + throw LdapUtils.convertLdapException(e); + } + } +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/AuthenticationSource.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/AuthenticationSource.java new file mode 100644 index 00000000..4f368e09 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/AuthenticationSource.java @@ -0,0 +1,40 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +/** + * An AuthenticationSource is responsible for providing the + * principal (user DN) and credentials to be used when creating a new context. + * + * @author Mattias Arthursson + * + */ +public interface AuthenticationSource { + /** + * Get the principal to use when creating an authenticated context. + * + * @return the principal (userDn). + */ + public String getPrincipal(); + + /** + * Get the credentials to use when creating an authenticated context. + * + * @return the credentials (password). + */ + public String getCredentials(); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/CollectingNameClassPairCallbackHandler.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/CollectingNameClassPairCallbackHandler.java new file mode 100644 index 00000000..ebb3a5dd --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/CollectingNameClassPairCallbackHandler.java @@ -0,0 +1,63 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import java.util.LinkedList; +import java.util.List; + +import javax.naming.NameClassPair; + +/** + * A NameClassPairCallbackHandler to collect all results in an internal List. + * + * @see LdapTemplate + * + * @author Mattias Arthursson + */ +public abstract class CollectingNameClassPairCallbackHandler implements + NameClassPairCallbackHandler { + + private List list = new LinkedList(); + + /** + * Get the assembled list. + * + * @return the list of all assembled objects. + */ + public List getList() { + return list; + } + + /** + * Pass on the supplied NameClassPair to + * {@link #getObjectFromNameClassPair(NameClassPair)} and add the result to + * the internal list. + */ + public void handleNameClassPair(NameClassPair nameClassPair) { + list.add(getObjectFromNameClassPair(nameClassPair)); + } + + /** + * Handle a NameClassPair and transform it to an Object of the desired type + * and with data from the NameClassPair. + * + * @param nameClassPair + * a NameClassPair from a search operation. + * @return an object constructed from the data in the NameClassPair. + */ + public abstract Object getObjectFromNameClassPair( + NameClassPair nameClassPair); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextAssembler.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextAssembler.java new file mode 100644 index 00000000..33403622 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextAssembler.java @@ -0,0 +1,36 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +/** + * Helper interface to be used by Dao implementations for assembling to and from + * context. Useful if we have assembler classes responsible for mapping to and + * from a specific entry. + * + * @author Mattias Arthursson + */ +public interface ContextAssembler extends ContextMapper { + /** + * Map the supplied object to the specified context. + * + * @param obj + * the object to read data from. + * @param ctx + * the context to map to. + */ + public void mapToContext(Object obj, Object ctx); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextExecutor.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextExecutor.java new file mode 100644 index 00000000..3feb41bf --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextExecutor.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +/** + * Interface for delegating an actual operation to be performed on a + * DirContext. For searches, use {@link SearchExecutor} in + * stead. A typical usage of this interface could be e.g.: + * + *

+ * ContextExecutor executor = new ContextExecutor() {
+ *     public Object executeWithContext(DirContext ctx) throws NamingException {
+ *         return ctx.lookup(dn);
+ *     }
+ * };
+ * 
+ * + * @see LdapTemplate#executeReadOnly(ContextExecutor) + * @see LdapTemplate#executeReadWrite(ContextExecutor) + * + * @author Mattias Arthursson + */ +public interface ContextExecutor { + /** + * Perform any operation on the context. + * + * @param ctx + * the DirContext to perform the operation on. + * @return any object resulting from the operation - might be null. + * @throws NamingException + * if the operation resulted in one. + */ + public Object executeWithContext(DirContext ctx) throws NamingException; +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextMapper.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextMapper.java new file mode 100644 index 00000000..2d82c243 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextMapper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.Binding; +import javax.naming.Name; +import javax.naming.directory.SearchResult; + +import org.springframework.ldap.core.support.AbstractContextMapper; +import org.springframework.ldap.core.support.DefaultDirObjectFactory; + +/** + * An interface used by LdapTemplate to map LDAP Contexts to beans. When a + * DirObjectFactory is set on the ContextSource, the objects returned from + * search and listBindings operations are + * automatically transformed to DirContext objects (when using the + * {@link DefaultDirObjectFactory} - which is typically the case, unless + * something else has been explicitly specified - you get a + * {@link DirContextAdapter} object). This object will then be passed to the + * ContextMapper implementation for transformation to the desired bean. + *

+ * ContextMapper implementations are typically stateless and thus reusable; they + * are ideal for implementing mapping logic in one place. + *

+ * Alternatively, consider using an {@link AttributesMapper} in stead. + * + * @see LdapTemplate#search(Name, String, ContextMapper) + * @see LdapTemplate#listBindings(Name, ContextMapper) + * @see LdapTemplate#lookup(Name, ContextMapper) + * @see AttributesMapper + * @see DefaultDirObjectFactory + * @see DirContextAdapter + * @see AbstractContextMapper + * + * @author Mattias Arthursson + */ +public interface ContextMapper { + /** + * Map a single LDAP Context to an object. The supplied Object + * ctx is the object from a single {@link SearchResult}, + * {@link Binding}, or a lookup operation. + * + * @param ctx + * the context to map to an object. Typically this will be a + * {@link DirContextAdapter} instance, unless a project specific + * DirObjectFactory has been specified on the + * ContextSource. + * @return an object built from the data in the context. + */ + public Object mapFromContext(Object ctx); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextMapperCallbackHandler.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextMapperCallbackHandler.java new file mode 100644 index 00000000..fccccb2a --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextMapperCallbackHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.Binding; +import javax.naming.NameClassPair; + +import org.apache.commons.lang.Validate; + +/** + * A CollectingNameClassPairCallbackHandler to wrap a ContextMapper. That is, + * the found object is extracted from each {@link Binding}, and then passed to + * the specified ContextMapper for translation. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + * @since 1.2 + */ +public class ContextMapperCallbackHandler extends + CollectingNameClassPairCallbackHandler { + private ContextMapper mapper; + + /** + * Constructs a new instance wrapping the supplied {@link ContextMapper}. + * + * @param mapper + * the mapper to be called for each entry. + */ + public ContextMapperCallbackHandler(ContextMapper mapper) { + Validate.notNull(mapper, "Mapper must not be empty"); + this.mapper = mapper; + } + + /** + * Cast the NameClassPair to a {@link Binding} and pass its object to + * the ContextMapper. + * + * @param nameClassPair + * a Binding instance. + * @return the Object returned from the mapper. + */ + public Object getObjectFromNameClassPair(NameClassPair nameClassPair) { + Binding binding = (Binding) nameClassPair; + Object object = binding.getObject(); + if (object == null) { + throw new ObjectRetrievalException( + "Binding did not contain any object."); + } + return mapper.mapFromContext(object); + } +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextSource.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextSource.java new file mode 100644 index 00000000..87656e44 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/ContextSource.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.directory.DirContext; + +import org.springframework.ldap.NamingException; + +/** + * Interface used by {@link LdapTemplate} to create LDAP contexts. + * + * @see org.springframework.ldap.core.LdapTemplate + * + * @author Adam Skogman + * @author Mattias Arthursson + */ +public interface ContextSource { + + /** + * Gets a read-only DirContext. The returned DirContext must be possible to + * perform read-only operations on. + * + * @return A DirContext instance, never null. + * @throws NamingException + * if some error occurs creating an DirContext. + */ + public DirContext getReadOnlyContext() throws NamingException; + + /** + * Gets a read-write DirContext. + * + * @return A DirContext instance, never null. + * @throws NamingException + * if some error occurs creating an DirContext. + */ + public DirContext getReadWriteContext() throws NamingException; +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/DefaultDnParserFactory.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/DefaultDnParserFactory.java new file mode 100644 index 00000000..3d685972 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/DefaultDnParserFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import java.io.StringReader; + +import org.springframework.ldap.core.DnParserImpl; + +/** + * A factory for creating DnParser instances. The actual implementation of + * DnParser is generated using javacc and should not be constructed directly. + * + * @author Mattias Arthursson + * + */ +public class DefaultDnParserFactory { + /** + * Create a new DnParser instance. + * + * @param string + * the DN String to be parsed. + * @return a new DnParser instance for parsing the supplied DN string. + */ + public static DnParser createDnParser(String string) { + return new DnParserImpl(new StringReader(string)); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/DefaultNameClassPairMapper.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/DefaultNameClassPairMapper.java new file mode 100644 index 00000000..9e61d85d --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/DefaultNameClassPairMapper.java @@ -0,0 +1,46 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.NameClassPair; +import javax.naming.NamingException; + +/** + * The default NameClassPairMapper implementation. This implementation simply + * takes the Name string from the supplied NameClassPair and returns it as + * result. + * + * @author Mattias Arthursson + * + */ +public class DefaultNameClassPairMapper implements NameClassPairMapper { + + /** + * Gets the Name from the supplied NameClassPair and returns it as the + * result. + * + * @param nameClassPair + * the NameClassPair to transform. + * @return the Name string from the NameClassPair. + */ + public Object mapFromNameClassPair(NameClassPair nameClassPair) + throws NamingException { + + return nameClassPair.getName(); + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextAdapter.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextAdapter.java new file mode 100644 index 00000000..3bd4b8ae --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextAdapter.java @@ -0,0 +1,1254 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameNotFoundException; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +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.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.support.LdapUtils; + +/** + * Adapter that implements the interesting methods of the DirContext interface. + * In particular it contains utility methods for getting and setting attributes. + * Using the + * {@link org.springframework.ldap.core.support.DefaultDirObjectFactory} in your + * ContextSource (which is the default) you will receive + * instances of this class from searches and lookups. This can be particularly + * useful when updating data, since this class implements + * {@link AttributeModificationsAware}, providing a + * {@link #getModificationItems()} method. When in update mode, an object of + * this class keeps track of the changes made to its attributes, making them + * available as an array of ModificationItem objects, suitable as + * input to {@link LdapTemplate#modifyAttributes(DirContextOperations)}. + * + * @see #setAttributeValue(String, Object) + * @see #setAttributeValues(String, Object[]) + * @see #getStringAttribute(String) + * @see #getStringAttributes(String) + * @see #getObjectAttribute(String) + * @see #addAttributeValue(String, Object) + * @see #removeAttributeValue(String, Object) + * @see #setUpdateMode(boolean) + * @see #isUpdateMode() + * + * @author Magnus Robertsson + * @author Andreas Ronge + * @author Adam Skogman + * @author Mattias Arthursson + */ +public class DirContextAdapter implements DirContextOperations { + + private static final boolean ORDER_DOESNT_MATTER = false; + + private static Log log = LogFactory.getLog(DirContextAdapter.class); + + private final Attributes originalAttrs; + + private DistinguishedName dn; + + private DistinguishedName base; + + private boolean updateMode = false; + + private Attributes updatedAttrs; + + /** + * Default constructor. + */ + public DirContextAdapter() { + this(null, null, null); + } + + /** + * Create a new adapter from the supplied dn. + * + * @param dn + * the dn. + */ + public DirContextAdapter(Name dn) { + this(null, dn); + } + + /** + * Create a new adapter from the supplied attributes and dn. + * + * @param attrs + * the attributes. + * @param dn + * the dn. + */ + public DirContextAdapter(Attributes attrs, Name dn) { + this(attrs, dn, null); + } + + /** + * Create a new adapter from the supplied attributes, dn, and base. + * + * @param attrs + * the attributes. + * @param dn + * the dn. + * @param base + * the base name. + */ + public DirContextAdapter(Attributes attrs, Name dn, Name base) { + if (attrs != null) { + this.originalAttrs = attrs; + } else { + this.originalAttrs = new BasicAttributes(true); + } + if (dn != null) { + this.dn = new DistinguishedName(dn.toString()); + } else { + this.dn = new DistinguishedName(); + } + if (base != null) { + this.base = new DistinguishedName(base.toString()); + } else { + this.base = new DistinguishedName(); + } + } + + /** + * Constructor for cloning an existing adapter. + * + * @param master + * The adapter to be copied. + */ + protected DirContextAdapter(DirContextAdapter master) { + this.originalAttrs = (Attributes) master.originalAttrs.clone(); + this.dn = master.dn; + this.updatedAttrs = (Attributes) master.updatedAttrs.clone(); + this.updateMode = master.updateMode; + } + + /** + * Sets the update mode. The update mode should be false for + * a new entry and true for an existing entry that is being + * updated. + * + * @param mode + * Update mode. + */ + public void setUpdateMode(boolean mode) { + this.updateMode = mode; + if (updateMode) { + updatedAttrs = new BasicAttributes(true); + } + } + + /* + * @see org.springframework.ldap.support.DirContextOperations#isUpdateMode() + */ + public boolean isUpdateMode() { + return updateMode; + } + + /* + * @see org.springframework.ldap.support.DirContextOperations#getNamesOfModifiedAttributes() + */ + public String[] getNamesOfModifiedAttributes() { + + List tmpList = new ArrayList(); + + NamingEnumeration attributesEnumeration; + if (isUpdateMode()) { + attributesEnumeration = updatedAttrs.getAll(); + } else { + attributesEnumeration = originalAttrs.getAll(); + } + + try { + while (attributesEnumeration.hasMore()) { + Attribute oneAttribute = (Attribute) attributesEnumeration + .next(); + tmpList.add(oneAttribute.getID()); + } + } catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } finally { + closeNamingEnumeration(attributesEnumeration); + } + + return (String[]) tmpList.toArray(new String[0]); + } + + private void closeNamingEnumeration(NamingEnumeration enumeration) { + try { + if (enumeration != null) { + enumeration.close(); + } + } catch (NamingException e) { + // Never mind this + } + } + + /* + * @see org.springframework.ldap.support.AttributeModificationsAware#getModificationItems() + */ + public ModificationItem[] getModificationItems() { + if (!updateMode) { + return new ModificationItem[0]; + } + + List tmpList = new LinkedList(); + NamingEnumeration attributesEnumeration = null; + try { + attributesEnumeration = updatedAttrs.getAll(); + + // find attributes that have been changed, removed or added + while (attributesEnumeration.hasMore()) { + Attribute oneAttr = (Attribute) attributesEnumeration.next(); + + collectModifications(oneAttr, tmpList); + } + } catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } finally { + closeNamingEnumeration(attributesEnumeration); + } + + if (log.isDebugEnabled()) { + log.debug("Number of modifications:" + tmpList.size()); + } + + return (ModificationItem[]) tmpList + .toArray(new ModificationItem[tmpList.size()]); + } + + /** + * Collect all modifications for the changed attribute. If no changes have + * been made, return immediately. If modifications have been made, and the + * original size as well as the updated size of the attribute is 1, replace + * the attribute. If the size of the updated attribute is 0, remove the + * attribute. Otherwise, the attribute is a multi-value attribute, in which + * case all modifications to the original value (removals and additions) + * will be collected individually. + * + * @param changedAttr + * the value of the changed attribute. + * @param modificationList + * the list in which to add the modifications. + * @throws NamingException + * if thrown by called Attribute methods. + */ + private void collectModifications(Attribute changedAttr, + List modificationList) throws NamingException { + Attribute currentAttribute = originalAttrs.get(changedAttr.getID()); + + if (changedAttr.equals(currentAttribute)) { + // No changes + return; + } else if (currentAttribute != null && currentAttribute.size() == 1 + && changedAttr.size() == 1) { + // Replace single-vale attribute. + modificationList.add(new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, changedAttr)); + } else if (changedAttr.size() == 0 && currentAttribute != null) { + // Attribute has been removed. + modificationList.add(new ModificationItem( + DirContext.REMOVE_ATTRIBUTE, changedAttr)); + } else if ((currentAttribute == null || currentAttribute.size() == 0) + && changedAttr.size() > 0) { + // Attribute has been added. + modificationList.add(new ModificationItem(DirContext.ADD_ATTRIBUTE, + changedAttr)); + } else if (changedAttr.size() > 0) { + // Change of multivalue Attribute. Collect additions and removals + // individually. + List myModifications = new LinkedList(); + collectModifications(currentAttribute, changedAttr, myModifications); + + if (myModifications.isEmpty()) { + // This means that the attributes are not equal, but the + // actual values are the same - thus the order must have + // changed. This should result in a REPLACE_ATTRIBUTE operation. + myModifications.add(new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, changedAttr)); + } + + modificationList.addAll(myModifications); + } + } + + private void collectModifications(Attribute originalAttr, + Attribute changedAttr, List modificationList) + throws NamingException { + + Attribute originalClone = (Attribute) originalAttr.clone(); + Attribute addedValuesAttribute = new BasicAttribute(originalAttr + .getID()); + + for (int i = 0; i < changedAttr.size(); i++) { + Object attributeValue = changedAttr.get(i); + if (!originalClone.remove(attributeValue)) { + addedValuesAttribute.add(attributeValue); + } + } + + // We have now traversed and removed all values from the original that + // were also present in the new values. The remaining values in the + // original must be the ones that were removed. + if (originalClone.size() > 0) { + modificationList.add(new ModificationItem( + DirContext.REMOVE_ATTRIBUTE, originalClone)); + } + + if (addedValuesAttribute.size() > 0) { + modificationList.add(new ModificationItem(DirContext.ADD_ATTRIBUTE, + addedValuesAttribute)); + } + } + + /** + * returns true if the attribute is empty. It is empty if a == null, size == + * 0 or get() == null or an exception if thrown when accessing the get + * method + */ + private boolean isEmptyAttribute(Attribute a) { + try { + return (a == null || a.size() == 0 || a.get() == null); + } catch (NamingException e) { + return true; + } + } + + /** + * Compare the existing attribute name with the values on the + * array values. The order of the array must be the same + * order as the existing multivalued attribute. + *

+ * Also handles the case where the values have been reset to the original + * values after a previous change. For example, changing + * [a,b,c] to [a,b] and then back to + * [a,b,c] again must result in this method returning + * true so the first change can be overwritten with the + * latest change. + * + * @param name + * Name of the original multi-valued attribute. + * @param values + * Array of values to check if they have been changed. + * @return true if there has been a change compared to original attribute, + * or a previous update + */ + private boolean isChanged(String name, Object[] values, boolean orderMatters) { + + Attribute orig = originalAttrs.get(name); + Attribute prev = updatedAttrs.get(name); + + // values == null and values.length == 0 is treated the same way + boolean emptyNewValue = (values == null || values.length == 0); + + // Setting to empty --------------------- + if (emptyNewValue) { + // FALSE: if both are null, it is not changed (both don't exist) + // TRUE: if new value is null and old value exists (should be + // removed) + // TODO Also include prev in null check + // TODO Also check if there is a single null element + if (orig != null) { + return true; + } + return false; + } + + // NOT setting to empty ------------------- + + // TRUE if existing value is null + if (orig == null) { + return true; + } + + // TRUE if different length compared to original attributes + if (orig.size() != values.length) { + return true; + } + + // TRUE if different length compared to previously updated attributes + if (prev != null && prev.size() != values.length) { + return true; + } + + // Check contents of arrays + + // Order DOES matter, e.g. first names + try { + for (int i = 0; i < orig.size(); i++) { + Object obj = orig.get(i); + // TRUE if one value is not equal + if (!(obj instanceof String)) { + return true; + } + if (orderMatters) { + // check only the string with same index + if (!values[i].equals(obj)) { + return true; + } + } else { + // check all strings + if (!ArrayUtils.contains(values, obj)) { + return true; + } + } + } + + } catch (NamingException e) { + // TRUE if we can't access the value + return true; + } + + if (prev != null) { + // Also check against updatedAttrs, since there might have been + // a previous update + try { + for (int i = 0; i < prev.size(); i++) { + Object obj = prev.get(i); + // TRUE if one value is not equal + if (!(obj instanceof String)) { + return true; + } + if (orderMatters) { + // check only the string with same index + if (!values[i].equals(obj)) { + return true; + } + } else { + // check all strings + if (!ArrayUtils.contains(values, obj)) { + return true; + } + } + } + + } catch (NamingException e) { + // TRUE if we can't access the value + return true; + } + } + // FALSE since we have compared all values + return false; + } + + /** + * Checks if an entry has a specific attribute. + * + * This method simply calls exists(String) with the attribute name. + * + * @param attr + * the attribute to check. + * @return true if attribute exists in entry. + */ + protected final boolean exists(Attribute attr) { + return exists(attr.getID()); + } + + /** + * Checks if the attribute exists in this entry, either it was read or it + * has been added and update() has been called. + * + * @param attrId + * id of the attribute to check. + * @return true if the attribute exists in the entry. + */ + protected final boolean exists(String attrId) { + return originalAttrs.get(attrId) != null; + } + + /* + * @see org.springframework.ldap.support.DirContextOperations#getStringAttribute(java.lang.String) + */ + public String getStringAttribute(String name) { + return (String) getObjectAttribute(name); + } + + /* + * @see org.springframework.ldap.support.DirContextOperations#getObjectAttribute(java.lang.String) + */ + public Object getObjectAttribute(String name) { + Attribute oneAttr = originalAttrs.get(name); + if (oneAttr == null) { + return null; + } + try { + return oneAttr.get(); + } catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } + } + + /* + * @see org.springframework.ldap.support.DirContextOperations#setAttributeValue(java.lang.String, + * java.lang.Object) + */ + public void setAttributeValue(String name, Object value) { + // new entry + if (!updateMode && value != null) { + originalAttrs.put(name, value); + } + + // updating entry + if (updateMode) { + BasicAttribute attribute = new BasicAttribute(name); + if (value != null) { + attribute.add(value); + } + updatedAttrs.put(attribute); + } + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.DirContextOperations#addAttributeValue(java.lang.String, + * java.lang.Object) + */ + public void addAttributeValue(String name, Object value) { + if (!updateMode && value != null) { + Attribute attr = originalAttrs.get(name); + if (attr == null) { + originalAttrs.put(name, value); + } else { + attr.add(value); + } + } else if (updateMode) { + Attribute attr = updatedAttrs.get(name); + if (attr == null) { + if (originalAttrs.get(name) == null) { + // No match in the original attributes - + // add a new Attribute to updatedAttrs + updatedAttrs.put(name, value); + } else { + // The attribute exists in the original attributes - clone + // that and add the new entry to it + attr = (Attribute) originalAttrs.get(name).clone(); + attr.add(value); + updatedAttrs.put(attr); + } + } else { + attr.add(value); + } + } + + // Null values will not be added + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.DirContextOperations#removeAttributeValue(java.lang.String, + * java.lang.Object) + */ + public void removeAttributeValue(String name, Object value) { + if (!updateMode && value != null) { + Attribute attr = originalAttrs.get(name); + if (attr != null) { + attr.remove(value); + if (attr.size() == 0) { + originalAttrs.remove(name); + } + } + } else if (updateMode) { + Attribute attr = updatedAttrs.get(name); + if (attr == null) { + if (originalAttrs.get(name) != null) { + attr = (Attribute) originalAttrs.get(name).clone(); + attr.remove(value); + updatedAttrs.put(attr); + } + } else { + attr.remove(value); + } + } + } + + /* + * @see org.springframework.ldap.support.DirContextOperations#setAttributeValues(java.lang.String, + * java.lang.Object[]) + */ + public void setAttributeValues(String name, Object[] values) { + setAttributeValues(name, values, ORDER_DOESNT_MATTER); + } + + /* + * @see org.springframework.ldap.support.DirContextOperations#setAttributeValues(java.lang.String, + * java.lang.Object[], boolean) + */ + public void setAttributeValues(String name, Object[] values, + boolean orderMatters) { + Attribute a = new BasicAttribute(name, orderMatters); + + for (int i = 0; values != null && i < values.length; i++) { + a.add(values[i]); + } + + // only change the original attribute if not in update mode + if (!updateMode && values != null && values.length > 0) { + // don't save empty arrays + originalAttrs.put(a); + } + + // possible to set an already existing attribute to an empty array + if (updateMode && isChanged(name, values, orderMatters)) { + updatedAttrs.put(a); + } + } + + /* + * @see org.springframework.ldap.support.DirContextOperations#update() + */ + public void update() { + NamingEnumeration attributesEnumeration = null; + + try { + attributesEnumeration = updatedAttrs.getAll(); + + // find what to update + while (attributesEnumeration.hasMore()) { + Attribute a = (Attribute) attributesEnumeration.next(); + + // if it does not exist it should be added + if (isEmptyAttribute(a)) { + originalAttrs.remove(a.getID()); + } else { + // Otherwise it should be set. + originalAttrs.put(a); + } + } + } catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } finally { + closeNamingEnumeration(attributesEnumeration); + } + + // Reset the attributes to be updated + updatedAttrs = new BasicAttributes(true); + } + + /* + * @see org.springframework.ldap.support.DirContextOperations#getStringAttributes(java.lang.String) + */ + public String[] getStringAttributes(String name) { + String[] attributes; + + Attribute attribute = originalAttrs.get(name); + if (attribute != null && attribute.size() > 0) { + attributes = new String[attribute.size()]; + for (int i = 0; i < attribute.size(); i++) { + try { + attributes[i] = (String) attribute.get(i); + } catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } + } + } else { + return null; + } + + return attributes; + } + + /* + * @see org.springframework.ldap.support.DirContextOperations#getAttributeSortedStringSet(java.lang.String) + */ + public SortedSet getAttributeSortedStringSet(String name) { + TreeSet attrSet = new TreeSet(); + + Attribute attribute = originalAttrs.get(name); + if (attribute != null) { + for (int i = 0; i < attribute.size(); i++) { + try { + attrSet.add(attribute.get(i)); + } catch (NamingException e) { + throw LdapUtils.convertLdapException(e); + } + } + } else { + return null; + } + + return attrSet; + } + + /** + * Set the supplied attribute. + * + * @param attribute + * the attribute to set. + */ + public void setAttribute(Attribute attribute) { + if (!updateMode) { + originalAttrs.put(attribute); + } else { + updatedAttrs.put(attribute); + } + } + + /** + * Get all attributes. + * + * @return all attributes. + */ + public Attributes getAttributes() { + return originalAttrs; + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(Name) + */ + public Attributes getAttributes(Name name) throws NamingException { + return getAttributes(name.toString()); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(String) + */ + public Attributes getAttributes(String name) throws NamingException { + if (!StringUtils.isEmpty(name)) { + throw new NameNotFoundException(); + } + return (Attributes) originalAttrs.clone(); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(Name, String[]) + */ + public Attributes getAttributes(Name name, String[] attrIds) + throws NamingException { + return getAttributes(name.toString(), attrIds); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(String, String[]) + */ + public Attributes getAttributes(String name, String[] attrIds) + throws NamingException { + if (!StringUtils.isEmpty(name)) { + throw new NameNotFoundException(); + } + + Attributes a = new BasicAttributes(true); + Attribute target; + for (int i = 0; i < attrIds.length; i++) { + target = originalAttrs.get(attrIds[i]); + if (target != null) { + a.put(target); + } + } + + return a; + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(javax.naming.Name, + * int, javax.naming.directory.Attributes) + */ + public void modifyAttributes(Name name, int modOp, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(String, int, + * Attributes) + */ + public void modifyAttributes(String name, int modOp, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(Name, + * ModificationItem[]) + */ + public void modifyAttributes(Name name, ModificationItem[] mods) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(String, + * ModificationItem[]) + */ + public void modifyAttributes(String name, ModificationItem[] mods) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#bind(Name, Object, Attributes) + */ + public void bind(Name name, Object obj, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#bind(String, Object, Attributes) + */ + public void bind(String name, Object obj, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#rebind(Name, Object, Attributes) + */ + public void rebind(Name name, Object obj, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#rebind(String, Object, Attributes) + */ + public void rebind(String name, Object obj, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#createSubcontext(Name, Attributes) + */ + public DirContext createSubcontext(Name name, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#createSubcontext(String, + * Attributes) + */ + public DirContext createSubcontext(String name, Attributes attrs) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#getSchema(Name) + */ + public DirContext getSchema(Name name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#getSchema(String) + */ + public DirContext getSchema(String name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#getSchemaClassDefinition(Name) + */ + public DirContext getSchemaClassDefinition(Name name) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#getSchemaClassDefinition(String) + */ + public DirContext getSchemaClassDefinition(String name) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#search(Name, Attributes, String[]) + */ + public NamingEnumeration search(Name name, Attributes matchingAttributes, + String[] attributesToReturn) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#search(String, Attributes, + * String[]) + */ + public NamingEnumeration search(String name, Attributes matchingAttributes, + String[] attributesToReturn) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#search(Name, Attributes) + */ + public NamingEnumeration search(Name name, Attributes matchingAttributes) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#search(String, Attributes) + */ + public NamingEnumeration search(String name, Attributes matchingAttributes) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#search(Name, String, + * SearchControls) + */ + public NamingEnumeration search(Name name, String filter, + SearchControls cons) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#search(String, String, + * SearchControls) + */ + public NamingEnumeration search(String name, String filter, + SearchControls cons) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#search(Name, String, Object[], + * SearchControls) + */ + public NamingEnumeration search(Name name, String filterExpr, + Object[] filterArgs, SearchControls cons) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.directory.DirContext#search(String, String, Object[], + * SearchControls) + */ + public NamingEnumeration search(String name, String filterExpr, + Object[] filterArgs, SearchControls cons) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#lookup(Name) + */ + public Object lookup(Name name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#lookup(String) + */ + public Object lookup(String name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#bind(Name, Object) + */ + public void bind(Name name, Object obj) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#bind(String, Object) + */ + public void bind(String name, Object obj) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#rebind(Name, Object) + */ + public void rebind(Name name, Object obj) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#rebind(String, Object) + */ + public void rebind(String name, Object obj) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#unbind(Name) + */ + public void unbind(Name name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#unbind(String) + */ + public void unbind(String name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#rename(Name, Name) + */ + public void rename(Name oldName, Name newName) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#rename(String, String) + */ + public void rename(String oldName, String newName) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#list(Name) + */ + public NamingEnumeration list(Name name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#list(String) + */ + public NamingEnumeration list(String name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#listBindings(Name) + */ + public NamingEnumeration listBindings(Name name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#listBindings(String) + */ + public NamingEnumeration listBindings(String name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#destroySubcontext(Name) + */ + public void destroySubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#destroySubcontext(String) + */ + public void destroySubcontext(String name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#createSubcontext(Name) + */ + public Context createSubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#createSubcontext(String) + */ + public Context createSubcontext(String name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#lookupLink(Name) + */ + public Object lookupLink(Name name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#lookupLink(String) + */ + public Object lookupLink(String name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#getNameParser(Name) + */ + public NameParser getNameParser(Name name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#getNameParser(String) + */ + public NameParser getNameParser(String name) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#composeName(Name, Name) + */ + public Name composeName(Name name, Name prefix) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#composeName(String, String) + */ + public String composeName(String name, String prefix) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#addToEnvironment(String, Object) + */ + public Object addToEnvironment(String propName, Object propVal) + throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#removeFromEnvironment(String) + */ + public Object removeFromEnvironment(String propName) throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#getEnvironment() + */ + public Hashtable getEnvironment() throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#close() + */ + public void close() throws NamingException { + throw new UnsupportedOperationException("Not implemented."); + } + + /** + * @see javax.naming.Context#getNameInNamespace() + */ + public String getNameInNamespace() { + DistinguishedName result = new DistinguishedName(dn); + result.prepend(base); + return result.toString(); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.support.DirContextOperations#getDn() + */ + public Name getDn() { + return dn; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.support.DirContextOperations#setDn(javax.naming.Name) + */ + public final void setDn(Name dn) { + if (!updateMode) { + this.dn = new DistinguishedName(dn.toString()); + } + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + // A subclass with identical values should NOT be considered equal. + // EqualsBuilder in commons-lang cannot handle subclasses correctly. + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return EqualsBuilder.reflectionEquals(this, obj); + } + + /** + * @see Object#hashCode() + */ + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append(getClass().getName()); + buf.append(":"); + if (dn != null) { + buf.append(" dn=" + dn); + } + buf.append(" {"); + + try { + for (NamingEnumeration i = originalAttrs.getAll(); i.hasMore();) { + Attribute attribute = (Attribute) i.next(); + if (attribute.size() == 1) { + buf.append(attribute.getID()); + buf.append('='); + buf.append(attribute.get()); + } else { + for (int j = 0; j < attribute.size(); j++) { + if (j > 0) { + buf.append(", "); + } + buf.append(attribute.getID()); + buf.append('['); + buf.append(j); + buf.append("]="); + buf.append(attribute.get(j)); + } + } + + if (i.hasMore()) { + buf.append(", "); + } + } + } catch (NamingException e) { + log.warn("Error in toString()"); + } + buf.append('}'); + + return buf.toString(); + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextOperations.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextOperations.java new file mode 100644 index 00000000..9a2cfceb --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextOperations.java @@ -0,0 +1,202 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import java.util.SortedSet; + +import javax.naming.Name; +import javax.naming.directory.DirContext; + +/** + * Interface for DirContextAdapter. + * + * @author Mattias Arthursson + * @see DirContextAdapter + */ +public interface DirContextOperations extends DirContext, + AttributeModificationsAware { + + /** + * Gets the update mode. An entry in update mode will keep track of its + * modifications so that they can be retrieved using + * {@link AttributeModificationsAware#getModificationItems()}. The update + * mode should be true for a new entry and true + * for an existing entry that is being updated. + * + * @return update mode. + */ + public boolean isUpdateMode(); + + /** + * Creates a String array of the names of the attributes which have been + * changed. + * + * If this is a new entry, all set entries will be in the list. If this is + * an updated entry, only changed and removed entries will be in the array. + * + * @return Array of String + */ + public String[] getNamesOfModifiedAttributes(); + + /** + * Get the value of a String attribute. If more than one attribute value + * exists for the specified attribute, only the first one will be returned. + * + * @param name + * name of the attribute. + * @return the value of the attribute. + * @throws ClassCastException + * if the value of the entry is not a String. + */ + public String getStringAttribute(String name); + + /** + * Get the value of an Object attribute. If more than one attribute value + * exists for the specified attribute, only the first one will be returned. + * + * @param name + * name of the attribute. + * @return the attribute value as an object if it exists, or + * null otherwise. + */ + public Object getObjectAttribute(String name); + + /** + * Set the with the name name to the value. + * + * @param name + * name of the attribute. + * @param value + * value to set the attribute to. + */ + public void setAttributeValue(String name, Object value); + + /** + * Sets a multivalue attribute, disregarding the order of the values. + * + * If value is null or value.length == 0 then the attribute will be removed. + * + * If update mode, changes will be made only if the array has more or less + * objects or if one or more object has changed. Reordering the objects will + * not cause an update. + * + * @param name + * The id of the attribute. + * @param values + * Attribute values. + */ + public void setAttributeValues(String name, Object[] values); + + /** + * Sets a multivalue attribute. + * + * If value is null or value.length == 0 then the attribute will be removed. + * + * If update mode, changes will be made if the array has more or less + * objects or if one or more string has changed. + * + * Reordering the objects will only cause an update if orderMatters is set + * to true. + * + * @param name + * The id of the attribute. + * @param values + * Attribute values. + * @param orderMatters + * If true, it will be changed even if data was + * just reordered. + */ + public void setAttributeValues(String name, Object[] values, + boolean orderMatters); + + /** + * Add a value to the Attribute with the specified name. If the Attribute + * doesn't exist it will be created. + * + * @param name + * the name of the Attribute to which the specified value should + * be added. + * @param value + * the Attribute value to add. + */ + public void addAttributeValue(String name, Object value); + + /** + * Remove a value from the Attribute with the specified name. If the + * Attribute doesn't exist, do nothing. + * + * @param name + * the name of the Attribute from which the specified value + * should be removed. + * @param value + * the value to remove. + */ + public void removeAttributeValue(String name, Object value); + + /** + * Update the attributes.This will mean that the getters (getStringAttribute + * methods) will return the updated values, and the modifications will be + * forgotten (i.e. + * {@link AttributeModificationsAware#getModificationItems()} will return an + * empty array. + */ + public void update(); + + /** + * Get all values of a String attribute. + * + * @param name + * name of the attribute. + * + * @return all registered values of the attribute. + */ + public String[] getStringAttributes(String name); + + /** + * Get all String values of the attribute as a SortedSet. + * + * @param name + * name of the attribute. + * @return a SortedSet containing all values of the + * attribute. + */ + public SortedSet getAttributeSortedStringSet(String name); + + /** + * Returns the DN relative to the base path. + * + * @return The distinguished name of the current context. + * + * @see DirContextAdapter#getNameInNamespace() + */ + public Name getDn(); + + /** + * Set the dn of this entry. + * + * @param dn + * the dn. + */ + public void setDn(Name dn); + + /* + * (non-Javadoc) + * + * @see javax.naming.Context#getNameInNamespace() + */ + public String getNameInNamespace(); +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextProcessor.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextProcessor.java new file mode 100644 index 00000000..907a682e --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextProcessor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +/** + * Interface to be called in search by {@link LdapTemplate} before and after the + * actual search and enumeration traversal. Implementations may be used to apply + * search controls on the Context and retrieve the results of + * such controls afterwards. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public interface DirContextProcessor { + /** + * Perform pre-processing on the supplied DirContext. + * + * @param ctx + * the DirContext instance. + * @throws NamingException + * if thrown by the underlying operation. + */ + public void preProcess(DirContext ctx) throws NamingException; + + /** + * Perform post-processing on the supplied DirContext. + * + * @param ctx + * the DirContext instance. + * @throws NamingException + * if thrown by the underlying operation. + */ + public void postProcess(DirContext ctx) throws NamingException; +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextProxy.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextProxy.java new file mode 100644 index 00000000..7e2a997d --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/DirContextProxy.java @@ -0,0 +1,34 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import javax.naming.directory.DirContext; + +/** + * Helper interface to be able to get hold of the target DirContext + * from proxies created by ContextSource proxies. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public interface DirContextProxy { + /** + * Get the target DirContext of the proxy. + * + * @return the target DirContext. + */ + DirContext getTargetContext(); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/DistinguishedName.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/DistinguishedName.java new file mode 100644 index 00000000..94bbff8e --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/DistinguishedName.java @@ -0,0 +1,738 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +import javax.naming.CompositeName; +import javax.naming.InvalidNameException; +import javax.naming.Name; +import javax.naming.ldap.Rdn; + +import org.apache.commons.lang.StringUtils; +import org.springframework.ldap.BadLdapGrammarException; +import org.springframework.ldap.support.ListComparator; + +/** + * Default implementation of a {@link Name} corresponding to an LDAP path. A + * Distinguished Name manipulation implementation is included in JDK1.5 + * (LdapName), but not in prior releases. + * + * A DistinguishedName is particularly useful when building or + * modifying an LDAP path dynamically, as escaping will be taken care of. + * + * A path is split into several names. The {@link Name} interface specifies that + * the most significant part be in position 0. + *

+ * Example: + * + *

+ *
The path
+ *
uid=adam.skogman, ou=People, ou=EU
+ *
Name[0]
+ *
ou=EU
+ *
Name[1]
+ *
ou=People
+ *
Name[2]
+ *
uid=adam.skogman
+ *
+ * + * Example: + * + *
+ * DistinguishedName path = new DistinguishedName("dc=jayway,dc=se");
+ * path.add("ou", "People");
+ * path.add("uid", "adam.skogman");
+ * String dn = path.toString();
+ * 
+ * + * will render uid=adam.skogman, ou=People, dc=jayway, dc=se + * + * @author Adam Skogman + * @author Mattias Arthursson + */ +public class DistinguishedName implements Name { + private static final boolean COMPACT = true; + + private static final boolean NON_COMPACT = false; + + private static final long serialVersionUID = 3514344371999042586L; + + /** + * An empty, unmodifiable DistinguishedName. + */ + public static final DistinguishedName EMPTY_PATH = new DistinguishedName(Collections.EMPTY_LIST); + + private List names; + + /** + * Construct a new DistinguishedName with no components. + */ + public DistinguishedName() { + names = new LinkedList(); + } + + /** + * Construct a new DistinguishedName from a String. + * + * @param path a String corresponding to a (syntactically) valid LDAP path. + */ + public DistinguishedName(String path) { + if (StringUtils.isBlank(path)) { + names = new LinkedList(); + } + else { + parse(path); + } + } + + /** + * Construct a new DistinguishedName from the supplied + * List of {@link LdapRdn} objects. + * + * @param list the components that this instance will consist of. + */ + public DistinguishedName(List list) { + this.names = list; + } + + /** + * Construct a new DistinguishedName from the supplied + * {@link Name}. The parts of the supplied {@link Name} must be + * syntactically correct {@link LdapRdn}s. + * + * @param name the {@link Name} to construct a new + * DistinguishedName from. + */ + public DistinguishedName(Name name) { + names = new LinkedList(); + for (int i = 0; i < name.size(); i++) { + names.add(new LdapRdn(name.get(i))); + } + } + + /** + * Parse the supplied String and make this instance represent the + * corresponding distinguished name. + * + * @param path the LDAP path to parse. + */ + protected void parse(String path) { + DnParser parser = DefaultDnParserFactory.createDnParser(unmangleCompositeName(path)); + DistinguishedName dn; + try { + dn = parser.dn(); + } + catch (ParseException e) { + throw new BadLdapGrammarException("Failed to parse DN", e); + } + catch (TokenMgrError e) { + throw new BadLdapGrammarException("Failed to parse DN", e); + } + this.names = dn.names; + } + + /** + * If path is surrounded by quotes, strip them. JNDI considers forward slash + * ('/') special, but LDAP doesn't. {@link CompositeName#toString()} tends + * to mangle a {@link Name} with a slash by surrounding it with quotes + * ('"'). + * + * @param path Path to check and possibly strip. + * @return A String with the possibly stripped path. + */ + private String unmangleCompositeName(String path) { + String tempPath; + // Check if CompositeName has mangled the name with quotes + if (path.startsWith("\"") && path.endsWith("\"")) { + tempPath = path.substring(1, path.length() - 1); + } + else { + tempPath = path; + } + return tempPath; + } + + /** + * Get the {@link LdapRdn} at a specified position. + * + * @param index the {@link LdapRdn} to retrieve. + * @return the {@link LdapRdn} at the requested position. + */ + public LdapRdn getLdapRdn(int index) { + return (LdapRdn) names.get(index); + } + + /** + * Get the {@link LdapRdn} with the specified key. If there are several + * {@link Rdn}s with the same key, the first one found (in order of + * significance) will be returned. + * + * @param key Attribute name of the {@link LdapRdn} to retrieve. + * @return the {@link LdapRdn} with the requested key. + * @throws IllegalArgumentException if no Rdn matches the given key. + */ + public LdapRdn getLdapRdn(String key) { + for (Iterator iter = names.iterator(); iter.hasNext();) { + LdapRdn rdn = (LdapRdn) iter.next(); + if (StringUtils.equals(rdn.getKey(), key)) { + return rdn; + } + } + + throw new IllegalArgumentException("No Rdn with the requested key: '" + key + "'"); + } + + /** + * Get the value of the {@link LdapRdnComponent} with the specified key + * (Attribute value). If there are several Rdns with the same key, the value + * of the first one found (in order of significance) will be returned. + * + * @param key Attribute name of the {@link LdapRdn} to retrieve. + * @return the value. + * @throws IllegalArgumentException if no Rdn matches the given key. + */ + public String getValue(String key) { + return getLdapRdn(key).getValue(); + } + + /** + * Get the name List. + * + * @return the list of {@link LdapRdn}s that this + * DistinguishedName consists of. + */ + public List getNames() { + return names; + } + + /** + * Get the String representation of this DistinguishedName. + * Add a space after each comma, to make it readable. + * + * @return a syntactically correct, properly escaped, nicely formatted + * String representation of the DistinguishedName. + */ + public String toString() { + return format(NON_COMPACT); + } + + /** + * Get the compact String representation of this + * DistinguishedName. Add no space after each comma, to make + * it compact. + * + * @return a syntactically correct, properly escaped String representation + * of the DistinguishedName. + */ + public String toCompactString() { + return format(COMPACT); + } + + /** + * Builds a complete LDAP path, ldap encoded, useful as a DN. + * + * Always uses lowercase, always separates with ", " i.e. comma and a space. + * + * @return the LDAP path. + */ + public String encode() { + return format(NON_COMPACT); + } + + private String format(boolean compact) { + // empty path + if (names.size() == 0) + return ""; + + StringBuffer buffer = new StringBuffer(256); + + ListIterator i = names.listIterator(names.size()); + while (i.hasPrevious()) { + LdapRdn rdn = (LdapRdn) i.previous(); + buffer.append(rdn.getLdapEncoded()); + + // add comma, except in last iteration + if (i.hasPrevious()) { + if (compact) { + buffer.append(","); + } + else { + buffer.append(", "); + + } + } + } + + return buffer.toString(); + } + + /** + * Builds a complete LDAP path, ldap and url encoded. Separates only with + * ",". + * + * @return the LDAP path, for use in an url. + */ + public String toUrl() { + StringBuffer buffer = new StringBuffer(256); + + for (int i = names.size() - 1; i >= 0; i--) { + LdapRdn n = (LdapRdn) names.get(i); + buffer.append(n.encodeUrl()); + if (i > 0) { + buffer.append(","); + } + } + return buffer.toString(); + } + + /** + * Determines if this DistinguishedName path contains another + * path. + * + * @param path the path to check. + * @return true if the supplied path is conained in this + * instance, false otherwise. + */ + public boolean contains(DistinguishedName path) { + + List shortlist = path.getNames(); + + // this path must be at least as long + if (getNames().size() < shortlist.size()) + return false; + + // must have names + if (shortlist.size() == 0) + return false; + + Iterator longiter = getNames().iterator(); + Iterator shortiter = shortlist.iterator(); + + LdapRdn longname = (LdapRdn) longiter.next(); + LdapRdn shortname = (LdapRdn) shortiter.next(); + + // find first match + while (!longname.equals(shortname) && longiter.hasNext()) { + longname = (LdapRdn) longiter.next(); + } + + // Done? + if (!shortiter.hasNext() && longname.equals(shortname)) + return true; + if (!longiter.hasNext()) + return false; + + // compare + while (longname.equals(shortname) && longiter.hasNext() && shortiter.hasNext()) { + longname = (LdapRdn) longiter.next(); + shortname = (LdapRdn) shortiter.next(); + } + + // Done + if (!shortiter.hasNext() && longname.equals(shortname)) + return true; + else + return false; + + } + + /** + * Add an LDAP path last in this DistinguishedName. E.g.: + * + *
+	 * DistinguishedName name1 = new DistinguishedName("c=SE, dc=jayway, dc=se");
+	 * DistinguishedName name2 = new DistinguishedName("ou=people");
+	 * name1.append(name2);
+	 * 
+ * + * will result in ou=people, c=SE, dc=jayway, dc=se + * + * @param path the path to append. + * @return this instance. + */ + public DistinguishedName append(DistinguishedName path) { + getNames().addAll(path.getNames()); + return this; + } + + /** + * Append a new {@link LdapRdn} using the supplied key and value. + * + * @param key the key of the {@link LdapRdn}. + * @param value the value of the {@link LdapRdn}. + * @return this instance. + */ + public DistinguishedName append(String key, String value) { + add(key, value); + return this; + } + + /** + * Add an LDAP path first in this DistinguishedName. E.g.: + * + *
+	 * DistinguishedName name1 = new DistinguishedName("ou=people");
+	 * DistinguishedName name2 = new DistinguishedName("c=SE, dc=jayway, dc=se");
+	 * name1.prepend(name2);
+	 * 
+ * + * will result in ou=people, c=SE, dc=jayway, dc=se + * + * @param path the path to prepend. + */ + public void prepend(DistinguishedName path) { + ListIterator i = path.getNames().listIterator(path.getNames().size()); + while (i.hasPrevious()) { + names.add(0, i.previous()); + } + } + + /** + * Remove the first part of this DistinguishedName. + * + * @return the removed entry. + */ + public LdapRdn removeFirst() { + return (LdapRdn) names.remove(0); + } + + /** + * Remove the supplied path from the beginning of this + * DistinguishedName if this instance starts with + * path. Useful for stripping base path suffix from a + * DistinguishedName. + * + * @param path the path to remove from the beginning of this instance. + */ + public void removeFirst(Name path) { + if (path != null && this.startsWith(path)) { + for (int i = 0; i < path.size(); i++) + this.removeFirst(); + } + } + + /** + * @see java.lang.Object#clone() + */ + public Object clone() { + + // just duplicate the list, the rdns are immutable. + LinkedList list = new LinkedList(getNames()); + + return new DistinguishedName(list); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + // A subclass with identical values should NOT be considered equal. + // EqualsBuilder in commons-lang cannot handle subclasses correctly. + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + + DistinguishedName name = (DistinguishedName) obj; + + // compare the lists + return getNames().equals(name.getNames()); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return this.getClass().hashCode() ^ getNames().hashCode(); + } + + /** + * Compare this instance to another object. Note that the comparison is done + * in order of significance, so the most significant Rdn is compared first, + * then the second and so on. + * + * @see javax.naming.Name#compareTo(java.lang.Object) + */ + public int compareTo(Object obj) { + DistinguishedName that = (DistinguishedName) obj; + ListComparator comparator = new ListComparator(); + return comparator.compare(this.names, that.names); + } + + public int size() { + return names.size(); + } + + public boolean isEmpty() { + return names.size() == 0; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#getAll() + */ + public Enumeration getAll() { + LinkedList strings = new LinkedList(); + for (Iterator iter = names.iterator(); iter.hasNext();) { + LdapRdn rdn = (LdapRdn) iter.next(); + strings.add(rdn.getLdapEncoded()); + } + + return Collections.enumeration(strings); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#get(int) + */ + public String get(int index) { + LdapRdn rdn = (LdapRdn) names.get(index); + return rdn.getLdapEncoded(); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#getPrefix(int) + */ + public Name getPrefix(int index) { + LinkedList newNames = new LinkedList(); + for (int i = 0; i < index; i++) { + newNames.add(names.get(i)); + } + + return new DistinguishedName(newNames); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#getSuffix(int) + */ + public Name getSuffix(int index) { + if (index > names.size()) { + throw new ArrayIndexOutOfBoundsException(); + } + + LinkedList newNames = new LinkedList(); + for (int i = index; i < names.size(); i++) { + newNames.add(names.get(i)); + } + + return new DistinguishedName(newNames); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#startsWith(javax.naming.Name) + */ + public boolean startsWith(Name name) { + if (name.size() == 0) { + return false; + } + + DistinguishedName start = null; + if (name instanceof DistinguishedName) { + start = (DistinguishedName) name; + } + else { + return false; + } + + if (start.size() > this.size()) { + return false; + } + + Iterator longiter = names.iterator(); + Iterator shortiter = start.getNames().iterator(); + + while (shortiter.hasNext()) { + Object longname = longiter.next(); + Object shortname = shortiter.next(); + + if (!longname.equals(shortname)) { + return false; + } + } + + // All names in shortiter matched. + return true; + } + + /** + * Determines if this DistinguishedName ends with a certian + * path. + * + * If the argument path is empty (no names in path) this method will return + * false. + * + * @param name The suffix to check for. + * + */ + public boolean endsWith(Name name) { + DistinguishedName path = null; + if (name instanceof DistinguishedName) { + path = (DistinguishedName) name; + } + else { + return false; + } + + List shortlist = path.getNames(); + + // this path must be at least as long + if (getNames().size() < shortlist.size()) + return false; + + // must have names + if (shortlist.size() == 0) + return false; + + ListIterator longiter = getNames().listIterator(getNames().size()); + ListIterator shortiter = shortlist.listIterator(shortlist.size()); + + while (shortiter.hasPrevious()) { + LdapRdn longname = (LdapRdn) longiter.previous(); + LdapRdn shortname = (LdapRdn) shortiter.previous(); + + if (!longname.equals(shortname)) + return false; + } + + // if short list ended, all were equal + return true; + + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#addAll(javax.naming.Name) + */ + public Name addAll(Name name) throws InvalidNameException { + return addAll(names.size(), name); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#addAll(int, javax.naming.Name) + */ + public Name addAll(int arg0, Name name) throws InvalidNameException { + DistinguishedName distinguishedName = null; + try { + distinguishedName = (DistinguishedName) name; + } + catch (ClassCastException e) { + throw new InvalidNameException("Invalid name type"); + } + + names.addAll(arg0, distinguishedName.getNames()); + return this; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#add(java.lang.String) + */ + public Name add(String string) throws InvalidNameException { + return add(names.size(), string); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#add(int, java.lang.String) + */ + public Name add(int index, String string) throws InvalidNameException { + try { + names.add(index, new LdapRdn(string)); + } + catch (BadLdapGrammarException e) { + throw new InvalidNameException("Failed to parse rdn '" + string + "'"); + } + return this; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#remove(int) + */ + public Object remove(int arg0) throws InvalidNameException { + LdapRdn rdn = (LdapRdn) names.remove(arg0); + return rdn.getLdapEncoded(); + } + + /** + * Remove the last part of this DistinguishedName. + * + * @return the removed {@link LdapRdn}. + */ + public LdapRdn removeLast() { + return (LdapRdn) names.remove(names.size() - 1); + } + + /** + * Add a new {@link LdapRdn} using the supplied key and value. + * + * @param key the key of the {@link LdapRdn}. + * @param value the value of the {@link LdapRdn}. + */ + public void add(String key, String value) { + names.add(new LdapRdn(key, value)); + } + + /** + * Add the supplied {@link LdapRdn} last in the list of Rdns. + * + * @param rdn the {@link LdapRdn} to add. + */ + public void add(LdapRdn rdn) { + names.add(rdn); + } + + /** + * Add the supplied {@link LdapRdn} att the specified index. + * + * @param idx the index at which to add the LdapRdn. + * @param rdn the LdapRdn to add. + */ + public void add(int idx, LdapRdn rdn) { + names.add(idx, rdn); + } + + /** + * Return an unmodifialbe copy of this instance. Note that the individual + * Rdns will still be possible to modify. + * + * @return a copy of this instance backed by an immutable list. + * @since 1.2 + */ + public DistinguishedName immutableDistinguishedName() { + return new DistinguishedName(Collections.unmodifiableList(getNames())); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/DistinguishedNameEditor.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/DistinguishedNameEditor.java new file mode 100644 index 00000000..051a4452 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/DistinguishedNameEditor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import java.beans.PropertyEditorSupport; + +/** + * Property editor for use with {@link DistinguishedName} instances. The + * {@link #setAsText(String)} method sets the value as an immutable + * instance of a DistinguishedName. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class DistinguishedNameEditor extends PropertyEditorSupport { + + /* + * (non-Javadoc) + * @see java.beans.PropertyEditorSupport#setAsText(java.lang.String) + */ + public void setAsText(String text) throws IllegalArgumentException { + if (text == null) { + setValue(null); + } + else { + setValue(new DistinguishedName(text).immutableDistinguishedName()); + } + } + + /* + * (non-Javadoc) + * @see java.beans.PropertyEditorSupport#getAsText() + */ + public String getAsText() { + Object theValue = getValue(); + if (theValue == null) { + return null; + } + else { + return ((DistinguishedName) theValue).toString(); + } + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/DnParser.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/DnParser.java new file mode 100644 index 00000000..b4cbb81d --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/DnParser.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +/** + * A parser for RFC2253-compliant Distinguished Names. + * + * @author Mattias Arthursson + * + */ +public interface DnParser { + /** + * Parse a full Distinguished Name. + * + * @return the DistinguishedName corresponding to the parsed + * stream. + */ + public DistinguishedName dn() throws ParseException; + + /** + * Parse a Relative Distinguished Name. + * + * @return the next rdn on the stream. + */ + public LdapRdn rdn() throws ParseException; +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapEncoder.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapEncoder.java new file mode 100644 index 00000000..8c54209f --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapEncoder.java @@ -0,0 +1,230 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import org.springframework.ldap.BadLdapGrammarException; + +/** + * Helper class to encode and decode ldap names and values. + * + * @author Adam Skogman + * @author Mattias Arthursson + */ +public class LdapEncoder { + + private static String[] nameEscapeTable = new String[96]; + + private static String[] filterEscapeTable = new String['\\' + 1]; + + static { + + // Name encoding table ------------------------------------- + + // all below 0x20 (control chars) + for (char c = 0; c < ' '; c++) { + nameEscapeTable[c] = "\\" + toTwoCharHex(c); + } + + nameEscapeTable['#'] = "\\#"; + nameEscapeTable[','] = "\\,"; + nameEscapeTable[';'] = "\\;"; + nameEscapeTable['='] = "\\="; + nameEscapeTable['+'] = "\\+"; + nameEscapeTable['<'] = "\\<"; + nameEscapeTable['>'] = "\\>"; + nameEscapeTable['\"'] = "\\\""; + nameEscapeTable['\\'] = "\\\\"; + + // Filter encoding table ------------------------------------- + + // fill with char itself + for (char c = 0; c < filterEscapeTable.length; c++) { + filterEscapeTable[c] = String.valueOf(c); + } + + // escapes (RFC2254) + filterEscapeTable['*'] = "\\2a"; + filterEscapeTable['('] = "\\28"; + filterEscapeTable[')'] = "\\29"; + filterEscapeTable['\\'] = "\\5c"; + filterEscapeTable[0] = "\\00"; + + } + + /** + * All static methods - not to be instantiated. + */ + private LdapEncoder() { + } + + protected static String toTwoCharHex(char c) { + + String raw = Integer.toHexString(c).toUpperCase(); + + if (raw.length() > 1) + return raw; + else + return "0" + raw; + } + + /** + * Escape a value for use in a filter. + * + * @param value + * the value to escape. + * @return a properly escaped representation of the supplied value. + */ + public static String filterEncode(String value) { + + if (value == null) + return null; + + // make buffer roomy + StringBuffer encodedValue = new StringBuffer(value.length() * 2); + + int length = value.length(); + + for (int i = 0; i < length; i++) { + + char c = value.charAt(i); + + if (c < filterEscapeTable.length) { + encodedValue.append(filterEscapeTable[c]); + } else { + // default: add the char + encodedValue.append(c); + } + } + + return encodedValue.toString(); + } + + /** + * LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI! + * + *
Escapes:
' ' [space] - "\ " [if first or last]
'#' + * [hash] - "\#"
',' [comma] - "\,"
';' [semicolon] - "\;"
'= + * [equals] - "\="
'+' [plus] - "\+"
'<' [less than] - + * "\<"
'>' [greater than] - "\>"
'"' [double quote] - + * "\""
'\' [backslash] - "\\"
+ * + * @param value + * the value to escape. + * @return The escaped value. + */ + static public String nameEncode(String value) { + + if (value == null) + return null; + + // make buffer roomy + StringBuffer encodedValue = new StringBuffer(value.length() * 2); + + int length = value.length(); + int last = length - 1; + + for (int i = 0; i < length; i++) { + + char c = value.charAt(i); + + // space first or last + if (c == ' ' && (i == 0 || i == last)) { + encodedValue.append("\\ "); + continue; + } + + if (c < nameEscapeTable.length) { + // check in table for escapes + String esc = nameEscapeTable[c]; + + if (esc != null) { + encodedValue.append(esc); + continue; + } + } + + // default: add the char + encodedValue.append(c); + } + + return encodedValue.toString(); + + } + + /** + * Decodes a value. Converts escaped chars to ordinary chars. + * + * @param value + * Trimmed value, so no leading an trailing blanks, except an + * escaped space last. + * @return The decoded value as a string. + * @throws BadLdapGrammarException + */ + static public String nameDecode(String value) + throws BadLdapGrammarException { + + if (value == null) + return null; + + // make buffer same size + StringBuffer decoded = new StringBuffer(value.length()); + + int i = 0; + while (i < value.length()) { + char currentChar = value.charAt(i); + if (currentChar == '\\') { + if (value.length() <= i + 1) { + // Ending with a single backslash is not allowed + throw new BadLdapGrammarException( + "Unexpected end of value " + "unterminated '\\'"); + } else { + char nextChar = value.charAt(i + 1); + if (nextChar == ',' || nextChar == '=' || nextChar == '+' + || nextChar == '<' || nextChar == '>' + || nextChar == '#' || nextChar == ';' + || nextChar == '\\' || nextChar == '\"' + || nextChar == ' ') { + // Normal backslash escape + decoded.append(nextChar); + i += 2; + } else { + if (value.length() <= i + 2) { + throw new BadLdapGrammarException( + "Unexpected end of value " + + "expected special or hex, found '" + + nextChar + "'"); + } else { + // This should be a hex value + String hexString = "" + nextChar + + value.charAt(i + 2); + decoded.append((char) Integer.parseInt(hexString, + 16)); + i += 3; + } + } + } + } else { + // This character wasn't escaped - just append it + decoded.append(currentChar); + i++; + } + } + + return decoded.toString(); + + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapOperations.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapOperations.java new file mode 100644 index 00000000..04dce7d2 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapOperations.java @@ -0,0 +1,1575 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import java.util.List; + +import javax.naming.Binding; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.directory.Attributes; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; + +import org.springframework.ldap.ContextNotEmptyException; +import org.springframework.ldap.NamingException; +import org.springframework.ldap.core.support.AbstractContextSource; +import org.springframework.ldap.support.LdapUtils; + +/** + * Interface that specifies a basic set of LDAP operations. Implemented by + * LdapTemplate, but it might be a useful option to use this interface in order + * to enhance testability. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public interface LdapOperations { + /** + * Perform a search using a particular {@link SearchExecutor} and context + * processor. Use this method only if especially needed - for the most cases + * there is an overloaded convenience method which calls this one with + * suitable argments. This method handles all the plumbing; getting a + * readonly context; looping through the NamingEnumeration + * and closing the context and enumeration. The actual search is delegated + * to the SearchExecutor and each found NameClassPair is + * passed to the CallbackHandler. Any encountered + * NamingException will be translated using + * {@link LdapUtils#convertLdapException(javax.naming.NamingException)}. + * + * @param se + * The SearchExecutor to use for performing the + * actual search. + * @param handler + * The NameClassPairCallbackHandler to which each + * found entry will be passed. + * @param processor + * DirContextProcessor for custom pre- and + * post-processing. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted as no entries being found. + */ + public void search(SearchExecutor se, NameClassPairCallbackHandler handler, + DirContextProcessor processor) throws NamingException; + + /** + * Perform a search using a particular {@link SearchExecutor}. Use this + * method only if especially needed - for the most cases there is an + * overloaded convenience method which calls this one with suitable + * argments. This method handles all the plumbing; getting a readonly + * context; looping through the NamingEnumeration and closing + * the context and enumeration. The actual search is delegated to the + * SearchExecutor and each found NameClassPair + * is passed to the CallbackHandler. Any encountered + * NamingException will be translated using the + * {@link LdapUtils#convertLdapException(javax.naming.NamingException)}. + * + * @param se + * The SearchExecutor to use for performing the + * actual search. + * @param handler + * The NameClassPairCallbackHandler to which each + * found entry will be passed. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted as no entries being found. + * @see #search(Name, String, AttributesMapper) + * @see #search(Name, String, ContextMapper) + */ + public void search(SearchExecutor se, NameClassPairCallbackHandler handler) + throws NamingException; + + /** + * Perform an operation (or series of operations) on a read-only context. + * This method handles the plumbing - getting a DirContext, + * translating any Exceptions and closing the context afterwards. This + * method is not intended for searches; use + * {@link #search(SearchExecutor, NameClassPairCallbackHandler)} or any of + * the overloaded search methods for this. + * + * @param ce + * The ContextExecutor to which the actual + * operation on the DirContext will be delegated. + * @return the result from the ContextExecutor's operation. + * @throws NamingException + * if the operation resulted in a NamingException. + * + * @see #search(SearchExecutor, NameClassPairCallbackHandler) + * @see #search(Name, String, AttributesMapper) + * @see #search(Name, String, ContextMapper) + */ + public Object executeReadOnly(ContextExecutor ce) throws NamingException; + + /** + * Perform an operation (or series of operations) on a read-write context. + * This method handles the plumbing - getting a DirContext, + * translating any exceptions and closing the context afterwards. This + * method is intended only for very particular cases, where there is no + * suitable method in this interface to use. + * + * @param ce + * The ContextExecutor to which the actual + * operation on the DirContext will be delegated. + * @return the result from the ContextExecutor's operation. + * @throws NamingException + * if the operation resulted in a NamingException. + * @see #bind(Name, Object, Attributes) + * @see #unbind(Name) + * @see #rebind(Name, Object, Attributes) + * @see #rename(Name, Name) + * @see #modifyAttributes(Name, ModificationItem[]) + */ + public Object executeReadWrite(ContextExecutor ce) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. The + * SearchScope specified in the supplied + * SearchControls will be used in the search. Note that if + * you are using a ContextMapper, the returningObjFlag needs + * to be set to true in the SearchControls. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. + * @param handler + * The NameClassPairCallbackHandler to supply the + * SearchResult to. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void search(Name base, String filter, SearchControls controls, + NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Search for all objects matching the supplied filter. See + * {@link #search(Name, String, SearchControls, NameClassPairCallbackHandler)} + * for details. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. + * @param handler + * The NameClassPairCallbackHandler to supply the + * SearchResult to. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void search(String base, String filter, SearchControls controls, + NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. The + * SearchScope specified in the supplied + * SearchControls will be used in the search. Note that if + * you are using a ContextMapper, the returningObjFlag needs + * to be set to true in the SearchControls. The given + * DirContextProcessor will be called before and after the + * search. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. + * @param handler + * The NameClassPairCallbackHandler to supply the + * SearchResult to. + * @param processor + * The DirContextProcessor to use before and after + * the search. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void search(Name base, String filter, SearchControls controls, + NameClassPairCallbackHandler handler, DirContextProcessor processor) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. The SearchScope + * specified in the supplied SearchControls will be used in + * the search. The given DirContextProcessor will be called + * before and after the search. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. + * @param mapper + * The AttributesMapper to use for translating + * each entry. + * @param processor + * The DirContextProcessor to use before and after + * the search. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(String base, String filter, SearchControls controls, + AttributesMapper mapper, DirContextProcessor processor) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. The SearchScope + * specified in the supplied SearchControls will be used in + * the search. The given DirContextProcessor will be called + * before and after the search. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. + * @param mapper + * The AttributesMapper to use for translating + * each entry. + * @param processor + * The DirContextProcessor to use before and after + * the search. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(Name base, String filter, SearchControls controls, + AttributesMapper mapper, DirContextProcessor processor) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Object returned + * in each SearchResult is supplied to the specified + * ContextMapper. The SearchScope specified + * in the supplied SearchControls will be used in the search. + * The given DirContextProcessor will be called before and + * after the search. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. If the + * returnObjFlag is not set in the SearchControls, + * this method will set it automatically, as this is required for + * the ContextMapper to work. + * @param mapper + * The ContextMapper to use for translating each + * entry. + * @param processor + * The DirContextProcessor to use before and after + * the search. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(String base, String filter, SearchControls controls, + ContextMapper mapper, DirContextProcessor processor) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Object returned + * in each SearchResult is supplied to the specified + * ContextMapper. The SearchScope specified + * in the supplied SearchControls will be used in the search. + * The given DirContextProcessor will be called before and + * after the search. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. If the + * returnObjFlag is not set in the SearchControls, + * this method will set it automatically, as this is required for + * the ContextMapper to work. + * @param mapper + * The ContextMapper to use for translating each + * entry. + * @param processor + * The DirContextProcessor to use before and after + * the search. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(Name base, String filter, SearchControls controls, + ContextMapper mapper, DirContextProcessor processor) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. See + * {@link #search(Name, String, SearchControls, NameClassPairCallbackHandler, DirContextProcessor)} + * for details. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. + * @param handler + * The NameClassPairCallbackHandler to supply the + * SearchResults to. + * @param processor + * The DirContextProcessor to use before and after + * the search. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void search(String base, String filter, SearchControls controls, + NameClassPairCallbackHandler handler, DirContextProcessor processor) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. Use the specified values + * for search scope and return objects flag. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param searchScope + * The search scope to set in SearchControls. + * @param returningObjFlag + * Whether the bound object should be returned in search results. + * Must be set to true if a + * ContextMapper is used. + * @param handler + * The NameClassPairCallbackHandler to supply the + * SearchResults to. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void search(Name base, String filter, int searchScope, + boolean returningObjFlag, NameClassPairCallbackHandler handler) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. Use the specified values + * for search scope and return objects flag. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param searchScope + * The search scope to set in SearchControls. + * @param returningObjFlag + * Whether the bound object should be returned in search results. + * Must be set to true if a + * ContextMapper is used. + * @param handler + * The NameClassPairCallbackHandler to supply the + * SearchResults to. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void search(String base, String filter, int searchScope, + boolean returningObjFlag, NameClassPairCallbackHandler handler) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. The default Search scope (SearchControls.SUBTREE_SCOPE) + * will be used and the returnObjects flag will be set to false. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param handler + * The NameClassPairCallbackHandler to supply the + * SearchResults to. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void search(Name base, String filter, + NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Each + * SearchResult is supplied to the specified + * NameClassPairCallbackHandler. The default Search scope (SearchControls.SUBTREE_SCOPE) + * will be used and the returnObjects flag will be set to false. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param handler + * The NameClassPairCallbackHandler to supply the + * SearchResults to. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void search(String base, String filter, + NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Only return any + * attributes mathing the specified attribute names. The Attributes in each + * SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param searchScope + * The search scope to set in SearchControls. + * @param attrs + * The attributes to return, null means returning + * all attributes. + * @param mapper + * The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(Name base, String filter, int searchScope, + String[] attrs, AttributesMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. Only return any + * attributes mathing the specified attribute names. The Attributes in each + * SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param searchScope + * The search scope to set in SearchControls. + * @param attrs + * The attributes to return, null means returning + * all attributes. + * @param mapper + * The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(String base, String filter, int searchScope, + String[] attrs, AttributesMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param searchScope + * The search scope to set in SearchControls. + * @param mapper + * The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(Name base, String filter, int searchScope, + AttributesMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param searchScope + * The search scope to set in SearchControls. + * @param mapper + * The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(String base, String filter, int searchScope, + AttributesMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. The default search scope will be used. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param mapper + * The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(Name base, String filter, AttributesMapper mapper) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes in + * each SearchResult is supplied to the specified + * AttributesMapper. The default search scope will be used. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param mapper + * The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * AttributesMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(String base, String filter, AttributesMapper mapper) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. Only return the + * supplied attributes. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param searchScope + * The search scope to set in SearchControls. + * @param attrs + * The attributes to return, null means all + * attributes. + * @param mapper + * The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(Name base, String filter, int searchScope, + String[] attrs, ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. Only return the + * supplied attributes. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param searchScope + * The search scope to set in SearchControls. + * @param attrs + * The attributes to return, null means all + * attributes. + * @param mapper + * The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(String base, String filter, int searchScope, + String[] attrs, ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param searchScope + * The search scope to set in SearchControls. + * @param mapper + * The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(Name base, String filter, int searchScope, + ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param searchScope + * The search scope to set in SearchControls. + * @param mapper + * The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(String base, String filter, int searchScope, + ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. The default + * search scope (SearchControls.SUBTREE_SCOPE) will be + * used. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param mapper + * The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(Name base, String filter, ContextMapper mapper) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. The default + * search scope (SearchControls.SUBTREE_SCOPE) will be + * used. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param mapper + * The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(String base, String filter, ContextMapper mapper) + throws NamingException; + + /** + * Search for all objects matching the supplied filter. The + * Object returned in each SearchResult is + * supplied to the specified ContextMapper. The default + * search scope (SearchControls.SUBTREE_SCOPE) will be + * used. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param mapper + * The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(String base, String filter, SearchControls controls, + ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Object returned + * in each SearchResult is supplied to the specified + * ContextMapper. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. If the + * returnObjFlag is not set in the SearchControls, + * this method will set it automatically, as this is required for + * the ContextMapper to work. + * @param mapper + * The ContextMapper to use for translating each + * entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(Name base, String filter, SearchControls controls, + ContextMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes + * returned in each SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. + * @param mapper + * The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(String base, String filter, SearchControls controls, + AttributesMapper mapper) throws NamingException; + + /** + * Search for all objects matching the supplied filter. The Attributes + * returned in each SearchResult is supplied to the specified + * AttributesMapper. + * + * @param base + * The base DN where the search should begin. + * @param filter + * The filter to use in the search. + * @param controls + * The SearchControls to use in the search. + * @param mapper + * The AttributesMapper to use for translating + * each entry. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List search(Name base, String filter, SearchControls controls, + AttributesMapper mapper) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Each resulting NameClassPair is + * supplied to the specified NameClassPairCallbackHandler. + * + * @param base + * The base DN where the list should be performed. + * @param handler + * The NameClassPairCallbackHandler to supply each + * {@link NameClassPair} to. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void list(String base, NameClassPairCallbackHandler handler) + throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Each resulting NameClassPair is + * supplied to the specified NameClassPairCallbackHandler. + * + * @param base + * The base DN where the list should be performed. + * @param handler + * The NameClassPairCallbackHandler to supply each + * {@link NameClassPair} to. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void list(Name base, NameClassPairCallbackHandler handler) + throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Pass all the found NameClassPair + * objects to the supplied NameClassPairMapper and return all + * the returned values as a List. + * + * @param base + * The base DN where the list should be performed. + * @param mapper + * The NameClassPairMapper to supply each + * {@link NameClassPair} to. + * @return a List containing the Objects returned from the + * Mapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List list(String base, NameClassPairMapper mapper) + throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Pass all the found NameClassPair + * objects to the supplied NameClassPairMapper and return all + * the returned values as a List. + * + * @param base + * The base DN where the list should be performed. + * @param mapper + * The NameClassPairMapper to supply each + * {@link NameClassPair} to. + * @return a List containing the Objects returned from the + * Mapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List list(Name base, NameClassPairMapper mapper) + throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. + * + * @param base + * The base DN where the list should be performed. + * @return a List containing the names of all the contexts bound to + * base. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List list(String base) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. + * + * @param base + * The base DN where the list should be performed. + * @return a List containing the names of all the contexts bound to + * base. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List list(Name base) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Each resulting Binding is supplied + * to the specified NameClassPairCallbackHandler. + * + * @param base + * The base DN where the list should be performed. + * @param handler + * The NameClassPairCallbackHandler to supply each + * {@link Binding} to. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void listBindings(final String base, + NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Each resulting Binding is supplied + * to the specified NameClassPairCallbackHandler. + * + * @param base + * The base DN where the list should be performed. + * @param handler + * The NameClassPairCallbackHandler to supply each + * {@link Binding} to. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public void listBindings(final Name base, + NameClassPairCallbackHandler handler) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Pass all the found Binding objects + * to the supplied NameClassPairMapper and return all the + * returned values as a List. + * + * @param base + * The base DN where the list should be performed. + * @param mapper + * The NameClassPairMapper to supply each + * {@link Binding} to. + * @return a List containing the Objects returned from the + * Mapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List listBindings(String base, NameClassPairMapper mapper) + throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. Pass all the found Binding objects + * to the supplied NameClassPairMapper and return all the + * returned values as a List. + * + * @param base + * The base DN where the list should be performed. + * @param mapper + * The NameClassPairMapper to supply each + * {@link Binding} to. + * @return a List containing the Objects returned from the + * Mapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List listBindings(Name base, NameClassPairMapper mapper) + throws NamingException; + + /** + * Perform a non-recursive listing of children of the given + * base. + * + * @param base + * The base DN where the list should be performed. + * @return a List containing the names of all the contexts + * bound to base. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List listBindings(final String base) throws NamingException; + + /** + * Perform a non-recursive listing of children of the given + * base. + * + * @param base + * The base DN where the list should be performed. + * @return a List containing the names of all the contexts + * bound to base. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List listBindings(final Name base) throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. The Object returned in each {@link Binding} is + * supplied to the specified ContextMapper. + * + * @param base + * The base DN where the list should be performed. + * @param mapper + * The ContextMapper to use for mapping the found + * object. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List listBindings(String base, ContextMapper mapper) + throws NamingException; + + /** + * Perform a non-recursive listing of the children of the given + * base. The Object returned in each {@link Binding} is + * supplied to the specified ContextMapper. + * + * @param base + * The base DN where the list should be performed. + * @param mapper + * The ContextMapper to use for mapping the found + * object. + * @return a List containing all entries received from the + * ContextMapper. + * @throws NamingException + * if any error occurs. Note that a + * NameNotFoundException will be ignored. Instead + * this is interpreted that no entries were found. + */ + public List listBindings(Name base, ContextMapper mapper) + throws NamingException; + + /** + * Lookup the supplied DN and return the found object. This will typically + * be a {@link DirContextAdapter}, unless the DirObjectFactory + * has been modified in the ContextSource. + * + * @param dn + * The distinguished name of the object to find. + * @return the found object, typically a {@link DirContextAdapter} instance. + * @throws NamingException + * if any error occurs. + * @see #lookupContext(Name) + * @see AbstractContextSource#setDirObjectFactory(Class) + */ + public Object lookup(Name dn) throws NamingException; + + /** + * Lookup the supplied DN and return the found object. This will typically + * be a {@link DirContextAdapter}, unless the DirObjectFactory + * has been modified in the ContextSource. + * + * @param dn + * The distinguished name of the object to find. + * @return the found object, typically a {@link DirContextAdapter} instance. + * @throws NamingException + * if any error occurs. + * @see #lookupContext(String) + * @see AbstractContextSource#setDirObjectFactory(Class) + */ + public Object lookup(String dn) throws NamingException; + + /** + * Convenience method to get the attributes of a specified DN and + * automatically pass them to an AttributesMapper. + * + * @param dn + * The distinguished name to find. + * @param mapper + * The AttributesMapper to use for mapping the + * found object. + * @return the object returned from the mapper. + * @throws NamingException + * if any error occurs. + */ + public Object lookup(Name dn, AttributesMapper mapper) + throws NamingException; + + /** + * Convenience method to get the attributes of a specified DN and + * automatically pass them to an AttributesMapper. + * + * @param dn + * The distinguished name to find. + * @param mapper + * The AttributesMapper to use for mapping the + * found object. + * @return the object returned from the mapper. + * @throws NamingException + * if any error occurs. + */ + public Object lookup(String dn, AttributesMapper mapper) + throws NamingException; + + /** + * Convenience method to lookup a specified DN and automatically pass the + * found object to a ContextMapper. + * + * @param dn + * The distinguished name to find. + * @param mapper + * The ContextMapper to use for mapping the found + * object. + * @return the object returned from the mapper. + * @throws NamingException + * if any error occurs. + */ + public Object lookup(Name dn, ContextMapper mapper) throws NamingException; + + /** + * Convenience method to lookup a specified DN and automatically pass the + * found object to a ContextMapper. + * + * @param dn + * The distinguished name to find. + * @param mapper + * The ContextMapper to use for mapping the found + * object. + * @return the object returned from the mapper. + * @throws NamingException + * if any error occurs. + */ + public Object lookup(String dn, ContextMapper mapper) + throws NamingException; + + /** + * Convenience method to get the specified attributes of a specified DN and + * automatically pass them to an AttributesMapper. + * + * @param dn + * The distinguished name to find. + * @param attributes + * The names of the attributes to pass to the mapper. + * @param mapper + * The AttributesMapper to use for mapping the + * found object. + * @return the object returned from the mapper. + * @throws NamingException + * if any error occurs. + */ + public Object lookup(Name dn, String[] attributes, AttributesMapper mapper) + throws NamingException; + + /** + * Convenience method to get the specified attributes of a specified DN and + * automatically pass them to an AttributesMapper. + * + * @param dn + * The distinguished name to find. + * @param attributes + * The names of the attributes to pass to the mapper. + * @param mapper + * The AttributesMapper to use for mapping the + * found object. + * @return the object returned from the mapper. + * @throws NamingException + * if any error occurs. + */ + public Object lookup(String dn, String[] attributes, AttributesMapper mapper) + throws NamingException; + + /** + * Convenience method to get the specified attributes of a specified DN and + * automatically pass them to a ContextMapper. + * + * @param dn + * The distinguished name to find. + * @param attributes + * The names of the attributes to pass to the mapper. + * @param mapper + * The ContextMapper to use for mapping the found + * object. + * @return the object returned from the mapper. + * @throws NamingException + * if any error occurs. + */ + public Object lookup(Name dn, String[] attributes, ContextMapper mapper) + throws NamingException; + + /** + * Convenience method to get the specified attributes of a specified DN and + * automatically pass them to a ContextMapper. + * + * @param dn + * The distinguished name to find. + * @param attributes + * The names of the attributes to pass to the mapper. + * @param mapper + * The ContextMapper to use for mapping the found + * object. + * @return the object returned from the mapper. + * @throws NamingException + * if any error occurs. + */ + public Object lookup(String dn, String[] attributes, ContextMapper mapper) + throws NamingException; + + /** + * Modify an entry in the LDAP tree using the supplied + * ModificationItems. + * + * @param dn + * The distinguished name of the node to modify. + * @param mods + * The modifications to perform. + * @throws NamingException + * if any error occurs. + * @see #modifyAttributes(DirContextOperations) + */ + public void modifyAttributes(Name dn, ModificationItem[] mods) + throws NamingException; + + /** + * Modify an entry in the LDAP tree using the supplied + * ModificationItems. + * + * @param dn + * The distinguished name of the node to modify. + * @param mods + * The modifications to perform. + * @throws NamingException + * if any error occurs. + * @see #modifyAttributes(DirContextOperations) + */ + public void modifyAttributes(String dn, ModificationItem[] mods) + throws NamingException; + + /** + * Create an entry in the LDAP tree. The attributes used to create the entry + * are either retrieved from the obj parameter or the + * attributes parameter (or both). One of these parameters + * may be null but not both. + * + * @param dn + * The distinguished name to bind the object and attributes to. + * @param obj + * The object to bind, may be null. Typically a + * DirContext implementation. + * @param attributes + * The attributes to bind, may be null. + * @throws NamingException + * if any error occurs. + * @see DirContextAdapter + */ + public void bind(Name dn, Object obj, Attributes attributes) + throws NamingException; + + /** + * Create an entry in the LDAP tree. The attributes used to create the entry + * are either retrieved from the obj parameter or the + * attributes parameter (or both). One of these parameters + * may be null but not both. + * + * @param dn + * The distinguished name to bind the object and attributes to. + * @param obj + * The object to bind, may be null. Typically a + * DirContext implementation. + * @param attributes + * The attributes to bind, may be null. + * @throws NamingException + * if any error occurs. + * @see DirContextAdapter + */ + public void bind(String dn, Object obj, Attributes attributes) + throws NamingException; + + /** + * Remove an entry from the LDAP tree. The entry must not have any children - + * if you suspect that the entry might have descendants, use + * {@link #unbind(Name, boolean)} in stead. + * + * @param dn + * The distinguished name of the entry to remove. + * @throws NamingException + * if any error occurs. + */ + public void unbind(Name dn) throws NamingException; + + /** + * Remove an entry from the LDAP tree. The entry must not have any children - + * if you suspect that the entry might have descendants, use + * {@link #unbind(Name, boolean)} in stead. + * + * @param dn + * The distinguished name to unbind. + * @throws NamingException + * if any error occurs. + */ + public void unbind(String dn) throws NamingException; + + /** + * Remove an entry from the LDAP tree, optionally removing all descendants + * in the process. + * + * @param dn + * The distinguished name to unbind. + * @param recursive + * Whether to unbind all subcontexts as well. If this parameter + * is false and the entry has children, the + * operation will fail. + * @throws NamingException + * if any error occurs. + */ + public void unbind(Name dn, boolean recursive) throws NamingException; + + /** + * Remove an entry from the LDAP tree, optionally removing all descendants + * in the process. + * + * @param dn + * The distinguished name to unbind. + * @param recursive + * Whether to unbind all subcontexts as well. If this parameter + * is false and the entry has children, the + * operation will fail. + * @throws NamingException + * if any error occurs. + */ + public void unbind(String dn, boolean recursive) throws NamingException; + + /** + * Remove an entry and replace it with a new one. The attributes used to + * create the entry are either retrieved from the obj + * parameter or the attributes parameter (or both). One of + * these parameters may be null but not both. This method + * assumes that the specified context already exists - if not it will fail. + * + * @param dn + * The distinguished name to rebind. + * @param obj + * The object to bind to the DN, may be null. + * Typically a DirContext implementation. + * @param attributes + * The attributes to bind, may be null. + * @throws NamingException + * if any error occurs. + * @see DirContextAdapter + */ + public void rebind(Name dn, Object obj, Attributes attributes) + throws NamingException; + + /** + * Remove an entry and replace it with a new one. The attributes used to + * create the entry are either retrieved from the obj + * parameter or the attributes parameter (or both). One of + * these parameters may be null but not both. This method + * assumes that the specified context already exists - if not it will fail. + * + * @param dn + * The distinguished name to rebind. + * @param obj + * The object to bind to the DN, may be null. + * Typically a DirContext implementation. + * @param attributes + * The attributes to bind, may be null. + * @throws NamingException + * if any error occurs. + * @see DirContextAdapter + */ + public void rebind(String dn, Object obj, Attributes attributes) + throws NamingException; + + /** + * Move an entry in the LDAP tree to a new location. + * + * @param oldDn + * The distinguished name of the entry to move; may not be + * null or empty. + * @param newDn + * The distinguished name where the entry should be moved; may + * not be null or empty. + * @throws ContextNotEmptyException + * if newDn is already bound + * @throws NamingException + * if any other error occurs. + */ + public void rename(final Name oldDn, final Name newDn) + throws NamingException; + + /** + * Move an entry in the LDAP tree to a new location. + * + * @param oldDn + * The distinguished name of the entry to move; may not be + * null or empty. + * @param newDn + * The distinguished name where the entry should be moved; may + * not be null or empty. + * @throws ContextNotEmptyException + * if newDn is already bound + * @throws NamingException + * if any other error occurs. + */ + public void rename(final String oldDn, final String newDn) + throws NamingException; + + /** + * Convenience method to lookup the supplied DN and automatically cast it to + * {@link DirContextOperations}. + * + * @param dn + * The distinguished name of the object to find. + * @return The found object, cast to {@link DirContextOperations}. + * @throws ClassCastException + * if an alternative DirObjectFactory has been + * registered with the ContextSource, causing + * the actual class of the returned object to be something else + * than {@link DirContextOperations}. + * @throws NamingException + * if any other error occurs. + * @see #lookup(Name) + * @see #modifyAttributes(DirContextOperations) + * @since 1.2 + */ + public DirContextOperations lookupContext(Name dn) throws NamingException, + ClassCastException; + + /** + * Convenience method to lookup the supplied DN and automatically cast it to + * {@link DirContextOperations}. + * + * @param dn + * The distinguished name of the object to find. + * @return The found object, cast to {@link DirContextOperations}. + * @throws ClassCastException + * if an alternative DirObjectFactory has been + * registered with the ContextSource, causing + * the actual class of the returned object to be something else + * than {@link DirContextOperations}. + * @throws NamingException + * if any other error occurs. + * @see #lookup(String) + * @see #modifyAttributes(DirContextOperations) + * @since 1.2 + */ + public DirContextOperations lookupContext(String dn) + throws NamingException, ClassCastException; + + /** + * Modify the attributes of the entry referenced by the supplied + * {@link DirContextOperations} instance. The DN to update will be the DN of + * the DirContextOperationsinstance, and the + * ModificationItem array is retrieved from the + * DirContextOperations instance using a call to + * {@link AttributeModificationsAware#getModificationItems()}. NB: + * The supplied instance needs to have been properly initialized; this means + * that if it hasn't been received from a lookup operation, + * its DN needs to be initialized and it must have been put in update mode ({@link DirContextAdapter#setUpdateMode(boolean)}). + *

+ * Typical use of this method would be as follows: + * + *

+     * public void update(Person person) {
+     *     DirContextOperations ctx = ldapOperations.lookupContext(person.getDn());
+     * 
+     *     ctx.setAttributeValue("description", person.getDescription());
+     *     ctx.setAttributeValue("telephoneNumber", person.getPhone());
+     *     // More modifications here
+     * 
+     *     ldapOperations.modifyAttributes(ctx);
+     * }
+     * 
+ * + * @param ctx + * the DirContextOperations instance to use in the update. + * @throws IllegalStateException + * if the supplied instance is not in update mode or has not + * been properly initialized. + * @throws NamingException + * if any other error occurs. + * @since 1.2 + * @see #lookupContext(Name) + * @see DirContextAdapter + */ + public void modifyAttributes(DirContextOperations ctx) + throws IllegalStateException, NamingException; +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapRdn.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapRdn.java new file mode 100644 index 00000000..4186ce09 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapRdn.java @@ -0,0 +1,258 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.springframework.ldap.BadLdapGrammarException; +import org.springframework.ldap.support.ListComparator; + +/** + * Datatype for a LDAP name, a part of a path. + * + * The name: uid=adam.skogman Key: uid Value: adam.skogman + * + * @author Adam Skogman + * @author Mattias Arthursson + */ +public class LdapRdn implements Serializable, Comparable { + private static final long serialVersionUID = 5681397547245228750L; + + private List components = new LinkedList(); + + /** + * Default constructor. Create an empty, uninitialized LdapRdn. + */ + public LdapRdn() { + } + + /** + * Parse the supplied string and construct this instance accordingly. + * + * @param string + * the string to parse. + */ + public LdapRdn(String string) { + DnParser parser = DefaultDnParserFactory.createDnParser(string); + LdapRdn rdn; + try { + rdn = parser.rdn(); + } catch (ParseException e) { + throw new BadLdapGrammarException("Failed to parse Rdn", e); + } catch (TokenMgrError e) { + throw new BadLdapGrammarException("Failed to parse Rdn", e); + } + this.components = rdn.components; + } + + /** + * Construct an LdapRdn using the supplied key and value. + * + * @param key + * the attribute name. + * @param value + * the attribute value. + */ + public LdapRdn(String key, String value) { + components.add(new LdapRdnComponent(key, value)); + } + + /** + * Add an LdapRdnComponent to this LdapRdn. + * + * @param rdnComponent + * the LdapRdnComponent to add.s + */ + public void addComponent(LdapRdnComponent rdnComponent) { + components.add(rdnComponent); + } + + /** + * Gets all components in this LdapRdn. + * + * @return the List of all LdapRdnComponents composing this LdapRdn. + */ + public List getComponents() { + return components; + } + + /** + * Gets the first LdapRdnComponent of this LdapRdn. + * + * @return The first LdapRdnComponent of this LdapRdn. + * @throws IndexOutOfBoundsException + * if there are no components in this Rdn. + */ + public LdapRdnComponent getComponent() { + return (LdapRdnComponent) components.get(0); + } + + /** + * Get the LdapRdnComponent at index idx. + * + * @param idx + * the 0-based index of the component to get. + * @return the LdapRdnComponent at index idx. + * @throws IndexOutOfBoundsException + * if there are no components in this Rdn. + */ + public LdapRdnComponent getComponent(int idx) { + return (LdapRdnComponent) components.get(idx); + } + + /** + * Get a properly rfc2253-encoded String representation of this LdapRdn. + * + * @return an escaped String corresponding to this LdapRdn. + * @throws IndexOutOfBoundsException + * if there are no components in this Rdn. + */ + public String getLdapEncoded() { + if (components.size() == 0) { + throw new IndexOutOfBoundsException("No components in Rdn."); + } + StringBuffer sb = new StringBuffer(100); + for (Iterator iter = components.iterator(); iter.hasNext();) { + LdapRdnComponent component = (LdapRdnComponent) iter.next(); + sb.append(component.encodeLdap()); + if (iter.hasNext()) { + sb.append("+"); + } + } + + return sb.toString(); + } + + /** + * Get a String representation of this LdapRdn for use in urls. + * + * @return a String representation of this LdapRdn for use in urls. + */ + public String encodeUrl() { + StringBuffer sb = new StringBuffer(100); + for (Iterator iter = components.iterator(); iter.hasNext();) { + LdapRdnComponent component = (LdapRdnComponent) iter.next(); + sb.append(component.encodeUrl()); + if (iter.hasNext()) { + sb.append("+"); + } + } + + return sb.toString(); + } + + /** + * Compare this LdapRdn to another object. + * + * @param obj + * the object to compare to. + * @throws ClassCastException + * if the supplied object is not an LdapRdn instance. + */ + public int compareTo(Object obj) { + LdapRdn that = (LdapRdn) obj; + Comparator comparator = new ListComparator(); + return comparator.compare(this.components, that.components); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + + LdapRdn that = (LdapRdn) obj; + return this.getComponents().equals(that.getComponents()); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return this.getClass().hashCode() ^ getComponents().hashCode(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + public String toString() { + return getLdapEncoded(); + } + + /** + * Get the value of this LdapRdn. Note that if this Rdn is multi-value the + * first value will be returned. E.g. for the Rdn + * cn=john doe+sn=doe, the return value would be + * john doe. + * + * @return the (first) value of this LdapRdn. + * @throws IndexOutOfBoundsException + * if there are no components in this Rdn. + */ + public String getValue() { + return getComponent().getValue(); + } + + /** + * Get the key of this LdapRdn. Note that if this Rdn is multi-value the + * first key will be returned. E.g. for the Rdn + * cn=john doe+sn=doe, the return value would be + * cn. + * + * @return the (first) key of this LdapRdn. + * @throws IndexOutOfBoundsException + * if there are no components in this Rdn. + */ + public String getKey() { + return getComponent().getKey(); + } + + /** + * Get the value of the LdapComponent with the specified key (Attribute + * name). + * + * @param key + * the key + * @return the value. + * @throws IllegalArgumentException + * if there is no component with the specified key. + */ + public String getValue(String key) { + for (Iterator iter = components.iterator(); iter.hasNext();) { + LdapRdnComponent component = (LdapRdnComponent) iter.next(); + if (StringUtils.equals(component.getKey(), key)) { + return component.getValue(); + } + } + + throw new IllegalArgumentException("No RdnComponent with the key " + + key); + } +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapRdnComponent.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapRdnComponent.java new file mode 100644 index 00000000..85cb324c --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapRdnComponent.java @@ -0,0 +1,195 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; + +/** + * Represents part of an LdapRdn. As specified in RFC2253 an LdapRdn may be + * composed of several attributes, separated by "+". An + * LdapRdnComponent represents one of these attributes. + * + * @author Mattias Arthursson + * + */ +public class LdapRdnComponent implements Comparable, Serializable { + private static final long serialVersionUID = -3296747972616243038L; + + public static final boolean DONT_DECODE_VALUE = false; + + private String key; + + private String value; + + /** + * Constructs an LdapRdnComponent without decoding the value. + * + * @param key the Attribute name. + * @param value the Attribute value. + */ + public LdapRdnComponent(String key, String value) { + this(key, value, DONT_DECODE_VALUE); + } + + /** + * Constructs an LdapRdnComponent, optionally decoding the value. + * + * @param key the Atttribute name. + * @param value the Attribute value. + * @param decodeValue if true the value is decoded (typically + * used when a DN is parsed from a String), otherwise the value is used as + * specified. + */ + public LdapRdnComponent(String key, String value, boolean decodeValue) { + Validate.notEmpty(key, "Key must not be empty"); + Validate.notEmpty(value, "Value must not be empty"); + + this.key = StringUtils.lowerCase(key); + if (decodeValue) { + this.value = LdapEncoder.nameDecode(value); + } + else { + this.value = value; + } + } + + /** + * Get the key (Attribute name) of this component. + * + * @return the key. + */ + public String getKey() { + return key; + } + + /** + * Set the key (Attribute name) of this component. + * + * @param key the key. + */ + public void setKey(String key) { + this.key = key; + } + + /** + * Get the (Attribute) value of this component. + * + * @return the value. + */ + public String getValue() { + return value; + } + + /** + * Set the (Attribute) value of this component. + * + * @param value the value. + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Encode key and value to ldap. + * + * @return Properly ldap escaped rdn. + */ + protected String encodeLdap() { + StringBuffer buff = new StringBuffer(key.length() + value.length() * 2); + + buff.append(key); + buff.append('='); + buff.append(LdapEncoder.nameEncode(value)); + + return buff.toString(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + public String toString() { + return getLdapEncoded(); + } + + /** + * @return The LdapRdn as a string where the value is LDAP-encoded. + */ + public String getLdapEncoded() { + return encodeLdap(); + } + + /** + * Get a String representation of this instance for use in URLs. + * + * @return a properly URL encoded representation of this instancs. + */ + public String encodeUrl() { + // Use the URI class to properly URL encode the value. + try { + URI valueUri = new URI(null, null, value, null); + return key + "=" + valueUri.toString(); + } + catch (URISyntaxException e) { + // This should really never happen... + return key + "=" + "value"; + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return key.hashCode() ^ value.hashCode(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj != null && obj.getClass() == LdapRdnComponent.class) { + LdapRdnComponent that = (LdapRdnComponent) obj; + return StringUtils.equalsIgnoreCase(this.key, that.key) + && StringUtils.equalsIgnoreCase(this.value, that.value); + + } + else { + return false; + } + } + + /** + * Compare this instance to the supplied object. + * + * @param obj the object to compare to. + * @throws ClassCastException if the object is not possible to cast to an + * LdapRdnComponent. + */ + public int compareTo(Object obj) { + LdapRdnComponent that = (LdapRdnComponent) obj; + return this.toString().compareTo(that.toString()); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java new file mode 100644 index 00000000..6769c249 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java @@ -0,0 +1,1339 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import java.util.List; + +import javax.naming.Binding; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; +import javax.naming.NamingEnumeration; +import javax.naming.PartialResultException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; + +import org.apache.commons.lang.Validate; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.ldap.NamingException; +import org.springframework.ldap.support.LdapUtils; + +/** + * Executes core LDAP functionality and helps to avoid common errors, relieving + * the user of the burden of looking up contexts, looping through + * NamingEnumerations and closing contexts. + *

+ * Note for Active Directory (AD) users: AD servers are apparently + * unable to handle referrals automatically, which causes a + * PartialResultException to be thrown whenever a referral is + * encountered in a search. To avoid this, set the + * ignorePartialResultException property to true. + * There is currently no way of manually handling these referrals in the form of + * ReferralException, i.e. either you get the exception (and + * your results are lost) or all referrals are ignored (if the server is unable + * to handle them properly. Neither is there any simple way to get notified that + * a PartialResultException has been ignored (other than in the + * log). + * + * @see org.springframework.ldap.core.ContextSource + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public class LdapTemplate implements LdapOperations, InitializingBean { + + private static final Log log = LogFactory.getLog(LdapTemplate.class); + + private static final int DEFAULT_SEARCH_SCOPE = SearchControls.SUBTREE_SCOPE; + + private static final boolean DONT_RETURN_OBJ_FLAG = false; + + private static final boolean RETURN_OBJ_FLAG = true; + + private static final String[] ALL_ATTRIBUTES = null; + + private ContextSource contextSource; + + private boolean ignorePartialResultException = false; + + /** + * Constructor for bean usage. + */ + public LdapTemplate() { + } + + /** + * Constructor to setup instance directly. + * + * @param contextSource + * the ContextSource to use. + */ + public LdapTemplate(ContextSource contextSource) { + this.contextSource = contextSource; + } + + /** + * Set the ContextSource. Call this method when the default constructor has + * been used. + * + * @param contextSource + * the ContextSource. + */ + public void setContextSource(ContextSource contextSource) { + this.contextSource = contextSource; + } + + /** + * Get the ContextSource. + * + * @return the ContextSource. + */ + public ContextSource getContextSource() { + return contextSource; + } + + /** + * Specify whether PartialResultException should be ignored + * in searches. AD servers typically have a problem with referrals. Normally + * a referral should be followed automatically, but this does not seem to + * work with AD servers. The problem manifests itself with a a + * PartialResultException being thrown when a referral is + * encountered by the server. Setting this property to true + * presents a workaround to this problem by causing + * PartialResultException to be ignored, so that the search + * method returns normally. Default value of this parameter is + * false. + * + * @param ignore + * true if PartialResultException + * should be ignored in searches, false otherwise. + * Default is false. + */ + public void setIgnorePartialResultException(boolean ignore) { + this.ignorePartialResultException = ignore; + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, int, boolean, + * org.springframework.ldap.core.NameClassPairCallbackHandler) + */ + public void search(Name base, String filter, int searchScope, + boolean returningObjFlag, NameClassPairCallbackHandler handler) { + + search(base, filter, getDefaultSearchControls(searchScope, + returningObjFlag, ALL_ATTRIBUTES), handler); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, int, boolean, + * org.springframework.ldap.core.NameClassPairCallbackHandler) + */ + public void search(String base, String filter, int searchScope, + boolean returningObjFlag, NameClassPairCallbackHandler handler) { + + search(base, filter, getDefaultSearchControls(searchScope, + returningObjFlag, ALL_ATTRIBUTES), handler); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.NameClassPairCallbackHandler) + */ + public void search(final Name base, final String filter, + final SearchControls controls, NameClassPairCallbackHandler handler) { + + // Create a SearchExecutor to perform the search. + SearchExecutor se = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) + throws javax.naming.NamingException { + return ctx.search(base, filter, controls); + } + }; + + search(se, handler); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.NameClassPairCallbackHandler) + */ + public void search(final String base, final String filter, + final SearchControls controls, NameClassPairCallbackHandler handler) { + + // Create a SearchExecutor to perform the search. + SearchExecutor se = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) + throws javax.naming.NamingException { + return ctx.search(base, filter, controls); + } + }; + + search(se, handler); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.NameClassPairCallbackHandler, + * org.springframework.ldap.core.DirContextProcessor) + */ + public void search(final Name base, final String filter, + final SearchControls controls, + NameClassPairCallbackHandler handler, DirContextProcessor processor) { + + // Create a SearchExecutor to perform the search. + SearchExecutor se = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) + throws javax.naming.NamingException { + return ctx.search(base, filter, controls); + } + }; + + search(se, handler, processor); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.NameClassPairCallbackHandler, + * org.springframework.ldap.core.DirContextProcessor) + */ + public void search(final String base, final String filter, + final SearchControls controls, + NameClassPairCallbackHandler handler, DirContextProcessor processor) { + + // Create a SearchExecutor to perform the search. + SearchExecutor se = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) + throws javax.naming.NamingException { + return ctx.search(base, filter, controls); + } + }; + + search(se, handler, processor); + } + + /** + * Perform a search operation, such as a search(), list() or listBindings(). + * This method handles all the plumbing; getting a readonly context; looping + * through the NamingEnumeration and closing the context and enumeration. It + * also calls the supplied DirContextProcessor before and after the search, + * respectively. This enables custom pre-processing and post-processing, + * like for example when handling paged results or other search controls. + *

+ * The actual list is delegated to the {@link SearchExecutor} and each + * {@link NameClassPair} (this might be a NameClassPair or a subclass + * thereof) is passed to the CallbackHandler. Any encountered + * NamingException will be translated using the NamingExceptionTranslator. + * + * @param se + * the SearchExecutor to use for performing the actual list. + * @param handler + * the NameClassPairCallbackHandler to which each found entry + * will be passed. + * @param processor + * DirContextProcessor for custom pre- and post-processing. May + * be null if no custom processing should take + * place. + * @throws NamingException + * if any error occurs. Note that a NameNotFoundException will + * be ignored. Instead this is interpreted that no entries were + * found. + */ + public void search(SearchExecutor se, NameClassPairCallbackHandler handler, + DirContextProcessor processor) { + DirContext ctx = contextSource.getReadOnlyContext(); + + NamingEnumeration results = null; + RuntimeException ex = null; + try { + processor.preProcess(ctx); + results = se.executeSearch(ctx); + + while (results.hasMore()) { + NameClassPair result = (NameClassPair) results.next(); + handler.handleNameClassPair(result); + } + } catch (NameNotFoundException e) { + // The base context was not found, which basically means + // that the search did not return any results. Just clean up and + // exit. + // Note that this may present problems if a DirContextProcessor was + // supplied - there's no guarantee that the postProcess() operation + // will go well after a NamingException has been thrown. It is + // however quite possible that information will be available for + // retrieval either way. + } catch (PartialResultException e) { + // Workaround for AD servers not handling referrals correctly. + if (ignorePartialResultException) { + log.debug("PartialResultException encountered and ignored", e); + } else { + ex = LdapUtils.convertLdapException(e); + } + } catch (javax.naming.NamingException e) { + ex = LdapUtils.convertLdapException(e); + } finally { + try { + processor.postProcess(ctx); + } catch (javax.naming.NamingException e) { + if (ex == null) { + ex = LdapUtils.convertLdapException(e); + } else { + // We already had an exception from above and should ignore + // this one. + log.debug("Ignoring Exception from postProcess, " + + "main exception thrown instead", e); + } + } + closeContextAndNamingEnumeration(ctx, results); + // If we got an exception it should be thrown. + if (ex != null) { + throw ex; + } + } + } + + /** + * Perform a search operation, such as a search(), list() or listBindings(). + * This method handles all the plumbing; getting a readonly context; looping + * through the NamingEnumeration and closing the context and enumeration. + *

+ * The actual list is delegated to the {@link SearchExecutor} and each + * {@link NameClassPair} (this might be a NameClassPair or a subclass + * thereof) is passed to the CallbackHandler. Any encountered + * NamingException will be translated using the NamingExceptionTranslator. + * + * @param se + * the SearchExecutor to use for performing the actual list. + * @param handler + * the NameClassPairCallbackHandler to which each found entry + * will be passed. + * @throws NamingException + * if any error occurs. Note that a NameNotFoundException will + * be ignored. Instead this is interpreted that no entries were + * found. + */ + public void search(SearchExecutor se, NameClassPairCallbackHandler handler) { + search(se, handler, new NullDirContextProcessor()); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, + * org.springframework.ldap.core.NameClassPairCallbackHandler) + */ + public void search(Name base, String filter, + NameClassPairCallbackHandler handler) { + + SearchControls controls = getDefaultSearchControls(DEFAULT_SEARCH_SCOPE, + DONT_RETURN_OBJ_FLAG, ALL_ATTRIBUTES); + if (handler instanceof ContextMapperCallbackHandler) { + assureReturnObjFlagSet(controls); + } + search(base, filter, controls, handler); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, + * org.springframework.ldap.core.NameClassPairCallbackHandler) + */ + public void search(String base, String filter, + NameClassPairCallbackHandler handler) { + + SearchControls controls = getDefaultSearchControls(DEFAULT_SEARCH_SCOPE, + DONT_RETURN_OBJ_FLAG, ALL_ATTRIBUTES); + if (handler instanceof ContextMapperCallbackHandler) { + assureReturnObjFlagSet(controls); + } + search(base, filter, controls, handler); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, int, java.lang.String[], + * org.springframework.ldap.core.AttributesMapper) + */ + public List search(Name base, String filter, int searchScope, + String[] attrs, AttributesMapper mapper) { + return search(base, filter, getDefaultSearchControls(searchScope, + DONT_RETURN_OBJ_FLAG, attrs), mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, int, java.lang.String[], + * org.springframework.ldap.core.AttributesMapper) + */ + public List search(String base, String filter, int searchScope, + String[] attrs, AttributesMapper mapper) { + return search(base, filter, getDefaultSearchControls(searchScope, + DONT_RETURN_OBJ_FLAG, attrs), mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, int, + * org.springframework.ldap.core.AttributesMapper) + */ + public List search(Name base, String filter, int searchScope, + AttributesMapper mapper) { + + return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, int, + * org.springframework.ldap.core.AttributesMapper) + */ + public List search(String base, String filter, int searchScope, + AttributesMapper mapper) { + + return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, org.springframework.ldap.core.AttributesMapper) + */ + public List search(Name base, String filter, AttributesMapper mapper) { + + return search(base, filter, DEFAULT_SEARCH_SCOPE, mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, org.springframework.ldap.core.AttributesMapper) + */ + public List search(String base, String filter, AttributesMapper mapper) { + + return search(base, filter, DEFAULT_SEARCH_SCOPE, mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, int, java.lang.String[], + * org.springframework.ldap.core.ContextMapper) + */ + public List search(Name base, String filter, int searchScope, + String[] attrs, ContextMapper mapper) { + + return search(base, filter, getDefaultSearchControls(searchScope, + RETURN_OBJ_FLAG, attrs), mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, int, java.lang.String[], + * org.springframework.ldap.core.ContextMapper) + */ + public List search(String base, String filter, int searchScope, + String[] attrs, ContextMapper mapper) { + + return search(base, filter, getDefaultSearchControls(searchScope, + RETURN_OBJ_FLAG, attrs), mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, int, org.springframework.ldap.core.ContextMapper) + */ + public List search(Name base, String filter, int searchScope, + ContextMapper mapper) { + + return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, int, org.springframework.ldap.core.ContextMapper) + */ + public List search(String base, String filter, int searchScope, + ContextMapper mapper) { + + return search(base, filter, searchScope, ALL_ATTRIBUTES, mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, org.springframework.ldap.core.ContextMapper) + */ + public List search(Name base, String filter, ContextMapper mapper) { + + return search(base, filter, DEFAULT_SEARCH_SCOPE, mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, org.springframework.ldap.core.ContextMapper) + */ + public List search(String base, String filter, ContextMapper mapper) { + + return search(base, filter, DEFAULT_SEARCH_SCOPE, mapper); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.ContextMapper) + */ + public List search(String base, String filter, SearchControls controls, + ContextMapper mapper) { + + return search(base, filter, controls, mapper, + new NullDirContextProcessor()); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.ContextMapper) + */ + public List search(Name base, String filter, SearchControls controls, + ContextMapper mapper) { + + return search(base, filter, controls, mapper, + new NullDirContextProcessor()); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.AttributesMapper) + */ + public List search(Name base, String filter, SearchControls controls, + AttributesMapper mapper) { + + return search(base, filter, controls, mapper, + new NullDirContextProcessor()); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.AttributesMapper) + */ + public List search(String base, String filter, SearchControls controls, + AttributesMapper mapper) { + return search(base, filter, controls, mapper, + new NullDirContextProcessor()); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.AttributesMapper, + * org.springframework.ldap.core.DirContextProcessor) + */ + public List search(String base, String filter, SearchControls controls, + AttributesMapper mapper, DirContextProcessor processor) { + AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler( + mapper); + search(base, filter, controls, handler, processor); + + return handler.getList(); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.AttributesMapper, + * org.springframework.ldap.core.DirContextProcessor) + */ + public List search(Name base, String filter, SearchControls controls, + AttributesMapper mapper, DirContextProcessor processor) { + AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler( + mapper); + search(base, filter, controls, handler, processor); + + return handler.getList(); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.LdapOperations#search(java.lang.String, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.ContextMapper, + * org.springframework.ldap.core.DirContextProcessor) + */ + public List search(String base, String filter, SearchControls controls, + ContextMapper mapper, DirContextProcessor processor) { + assureReturnObjFlagSet(controls); + ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler( + mapper); + search(base, filter, controls, handler, processor); + + return handler.getList(); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.LdapOperations#search(javax.naming.Name, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.ContextMapper, + * org.springframework.ldap.core.DirContextProcessor) + */ + public List search(Name base, String filter, SearchControls controls, + ContextMapper mapper, DirContextProcessor processor) { + assureReturnObjFlagSet(controls); + ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler( + mapper); + search(base, filter, controls, handler, processor); + + return handler.getList(); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#list(java.lang.String, + * org.springframework.ldap.core.NameClassPairCallbackHandler) + */ + public void list(final String base, NameClassPairCallbackHandler handler) { + SearchExecutor searchExecutor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) + throws javax.naming.NamingException { + return ctx.list(base); + } + }; + + search(searchExecutor, handler); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#list(javax.naming.Name, + * org.springframework.ldap.core.NameClassPairCallbackHandler) + */ + public void list(final Name base, NameClassPairCallbackHandler handler) { + SearchExecutor searchExecutor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) + throws javax.naming.NamingException { + return ctx.list(base); + } + }; + + search(searchExecutor, handler); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#list(java.lang.String, + * org.springframework.ldap.core.NameClassPairMapper) + */ + public List list(String base, NameClassPairMapper mapper) { + CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler( + mapper); + list(base, handler); + return handler.getList(); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#list(javax.naming.Name, + * org.springframework.ldap.core.NameClassPairMapper) + */ + public List list(Name base, NameClassPairMapper mapper) { + CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler( + mapper); + list(base, handler); + return handler.getList(); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#list(javax.naming.Name) + */ + public List list(final Name base) { + return list(base, new DefaultNameClassPairMapper()); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#list(java.lang.String) + */ + public List list(final String base) { + return list(base, new DefaultNameClassPairMapper()); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#listBindings(java.lang.String, + * org.springframework.ldap.core.NameClassPairCallbackHandler) + */ + public void listBindings(final String base, + NameClassPairCallbackHandler handler) { + SearchExecutor searchExecutor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) + throws javax.naming.NamingException { + return ctx.listBindings(base); + } + }; + + search(searchExecutor, handler); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#listBindings(javax.naming.Name, + * org.springframework.ldap.core.NameClassPairCallbackHandler) + */ + public void listBindings(final Name base, + NameClassPairCallbackHandler handler) { + SearchExecutor searchExecutor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) + throws javax.naming.NamingException { + return ctx.listBindings(base); + } + }; + + search(searchExecutor, handler); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#listBindings(java.lang.String, + * org.springframework.ldap.core.NameClassPairMapper) + */ + public List listBindings(String base, NameClassPairMapper mapper) { + CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler( + mapper); + listBindings(base, handler); + return handler.getList(); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#listBindings(javax.naming.Name, + * org.springframework.ldap.core.NameClassPairMapper) + */ + public List listBindings(Name base, NameClassPairMapper mapper) { + CollectingNameClassPairCallbackHandler handler = new MappingCollectingNameClassPairCallbackHandler( + mapper); + listBindings(base, handler); + return handler.getList(); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#listBindings(java.lang.String) + */ + public List listBindings(final String base) { + return listBindings(base, new DefaultNameClassPairMapper()); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#listBindings(javax.naming.Name) + */ + public List listBindings(final Name base) { + return listBindings(base, new DefaultNameClassPairMapper()); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#listBindings(java.lang.String, + * org.springframework.ldap.core.ContextMapper) + */ + public List listBindings(String base, ContextMapper mapper) { + + ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler( + mapper); + listBindings(base, handler); + + return handler.getList(); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#listBindings(javax.naming.Name, + * org.springframework.ldap.core.ContextMapper) + */ + public List listBindings(Name base, ContextMapper mapper) { + + ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler( + mapper); + listBindings(base, handler); + + return handler.getList(); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#executeReadOnly(org.springframework.ldap.core.DirContextProcessor) + */ + public Object executeReadOnly(ContextExecutor ce) { + DirContext ctx = contextSource.getReadOnlyContext(); + return executeWithContext(ce, ctx); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#executeReadWrite(org.springframework.ldap.core.DirContextProcessor) + */ + public Object executeReadWrite(ContextExecutor ce) { + DirContext ctx = contextSource.getReadWriteContext(); + return executeWithContext(ce, ctx); + } + + private Object executeWithContext(ContextExecutor ce, DirContext ctx) { + try { + return ce.executeWithContext(ctx); + } catch (javax.naming.NamingException e) { + throw LdapUtils.convertLdapException(e); + } finally { + closeContext(ctx); + } + } + + /* + * @see org.springframework.ldap.core.LdapOperations#lookup(javax.naming.Name) + */ + public Object lookup(final Name dn) { + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + return ctx.lookup(dn); + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#lookup(java.lang.String) + */ + public Object lookup(final String dn) { + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + return ctx.lookup(dn); + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#lookup(javax.naming.Name, + * org.springframework.ldap.core.AttributesMapper) + */ + public Object lookup(final Name dn, final AttributesMapper mapper) { + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + Attributes attributes = ctx.getAttributes(dn); + return mapper.mapFromAttributes(attributes); + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#lookup(java.lang.String, + * org.springframework.ldap.core.AttributesMapper) + */ + public Object lookup(final String dn, final AttributesMapper mapper) { + + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + Attributes attributes = ctx.getAttributes(dn); + return mapper.mapFromAttributes(attributes); + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#lookup(javax.naming.Name, + * org.springframework.ldap.core.ContextMapper) + */ + public Object lookup(final Name dn, final ContextMapper mapper) { + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + Object object = ctx.lookup(dn); + return mapper.mapFromContext(object); + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#lookup(java.lang.String, + * org.springframework.ldap.core.ContextMapper) + */ + public Object lookup(final String dn, final ContextMapper mapper) { + + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + Object object = ctx.lookup(dn); + return mapper.mapFromContext(object); + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#lookup(javax.naming.Name, + * java.lang.String[], org.springframework.ldap.core.AttributesMapper) + */ + public Object lookup(final Name dn, final String[] attributes, + final AttributesMapper mapper) { + + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + Attributes filteredAttributes = ctx.getAttributes(dn, + attributes); + return mapper.mapFromAttributes(filteredAttributes); + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#lookup(java.lang.String, + * java.lang.String[], org.springframework.ldap.core.AttributesMapper) + */ + public Object lookup(final String dn, final String[] attributes, + final AttributesMapper mapper) { + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + Attributes filteredAttributes = ctx.getAttributes(dn, + attributes); + return mapper.mapFromAttributes(filteredAttributes); + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#lookup(javax.naming.Name, + * java.lang.String[], org.springframework.ldap.core.ContextMapper) + */ + public Object lookup(final Name dn, final String[] attributes, + final ContextMapper mapper) { + + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + Attributes filteredAttributes = ctx.getAttributes(dn, + attributes); + DirContextAdapter contextAdapter = new DirContextAdapter( + filteredAttributes, dn); + return mapper.mapFromContext(contextAdapter); + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#lookup(java.lang.String, + * java.lang.String[], org.springframework.ldap.core.ContextMapper) + */ + public Object lookup(final String dn, final String[] attributes, + final ContextMapper mapper) { + + return executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + Attributes filteredAttributes = ctx.getAttributes(dn, + attributes); + DistinguishedName name = new DistinguishedName(dn); + DirContextAdapter contextAdapter = new DirContextAdapter( + filteredAttributes, name); + return mapper.mapFromContext(contextAdapter); + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#modifyAttributes(javax.naming.Name, + * javax.naming.directory.ModificationItem[]) + */ + public void modifyAttributes(final Name dn, final ModificationItem[] mods) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + ctx.modifyAttributes(dn, mods); + return null; + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#modifyAttributes(java.lang.String, + * javax.naming.directory.ModificationItem[]) + */ + public void modifyAttributes(final String dn, final ModificationItem[] mods) { + + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + ctx.modifyAttributes(dn, mods); + return null; + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#bind(javax.naming.Name, + * java.lang.Object, javax.naming.directory.Attributes) + */ + public void bind(final Name dn, final Object obj, + final Attributes attributes) { + + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + ctx.bind(dn, obj, attributes); + return null; + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#bind(java.lang.String, + * java.lang.Object, javax.naming.directory.Attributes) + */ + public void bind(final String dn, final Object obj, + final Attributes attributes) { + + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + ctx.bind(dn, obj, attributes); + return null; + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#unbind(javax.naming.Name) + */ + public void unbind(final Name dn) { + doUnbind(dn); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#unbind(java.lang.String) + */ + public void unbind(final String dn) { + doUnbind(dn); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#unbind(javax.naming.Name, + * boolean) + */ + public void unbind(final Name dn, boolean recursive) { + if (!recursive) { + doUnbind(dn); + } else { + doUnbindRecursively(dn); + } + } + + /* + * @see org.springframework.ldap.core.LdapOperations#unbind(java.lang.String, + * boolean) + */ + public void unbind(final String dn, boolean recursive) { + if (!recursive) { + doUnbind(dn); + } else { + doUnbindRecursively(dn); + } + } + + private void doUnbind(final Name dn) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + ctx.unbind(dn); + return null; + } + }); + } + + private void doUnbind(final String dn) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + ctx.unbind(dn); + return null; + } + }); + } + + private void doUnbindRecursively(final Name dn) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) { + deleteRecursively(ctx, new DistinguishedName(dn)); + return null; + } + }); + } + + private void doUnbindRecursively(final String dn) { + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + deleteRecursively(ctx, new DistinguishedName(dn)); + return null; + } + }); + } + + /** + * Delete all subcontexts including the current one recursively. + * + * @param ctx + * The context to use for deleting. + * @param name + * The starting point to delete recursively. + * @throws NamingException + * if any error occurs + */ + protected void deleteRecursively(DirContext ctx, DistinguishedName name) { + + NamingEnumeration enumeration = null; + try { + enumeration = ctx.listBindings(name); + while (enumeration.hasMore()) { + Binding binding = (Binding) enumeration.next(); + DistinguishedName childName = new DistinguishedName(binding + .getName()); + childName.prepend((DistinguishedName) name); + deleteRecursively(ctx, childName); + } + ctx.unbind(name); + if (log.isDebugEnabled()) { + log.debug("Entry " + name + " deleted"); + } + } catch (javax.naming.NamingException e) { + throw LdapUtils.convertLdapException(e); + } finally { + try { + enumeration.close(); + } catch (Exception e) { + // Never mind this + } + } + } + + /* + * @see org.springframework.ldap.core.LdapOperations#rebind(javax.naming.Name, + * java.lang.Object, javax.naming.directory.Attributes) + */ + public void rebind(final Name dn, final Object obj, + final Attributes attributes) { + + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + ctx.rebind(dn, obj, attributes); + return null; + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#rebind(java.lang.String, + * java.lang.Object, javax.naming.directory.Attributes) + */ + public void rebind(final String dn, final Object obj, + final Attributes attributes) { + + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + ctx.rebind(dn, obj, attributes); + return null; + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#rename(javax.naming.Name, + * javax.naming.Name) + */ + public void rename(final Name oldDn, final Name newDn) { + + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + ctx.rename(oldDn, newDn); + return null; + } + }); + } + + /* + * @see org.springframework.ldap.core.LdapOperations#rename(java.lang.String, + * java.lang.String) + */ + public void rename(final String oldDn, final String newDn) { + + executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws javax.naming.NamingException { + ctx.rename(oldDn, newDn); + return null; + } + }); + } + + /* + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + if (contextSource == null) { + throw new IllegalArgumentException( + "Property 'contextSource' must be set."); + } + } + + private void closeContextAndNamingEnumeration(DirContext ctx, + NamingEnumeration results) { + + closeNamingEnumeration(results); + closeContext(ctx); + } + + /** + * Close the supplied DirContext if it is not null. Swallow any exceptions, + * as this is only for cleanup. + * + * @param ctx + * the context to close. + */ + private void closeContext(DirContext ctx) { + if (ctx != null) { + try { + ctx.close(); + } catch (Exception e) { + // Never mind this. + } + } + } + + /** + * Close the supplied NamingEnumeration if it is not null. Swallow any + * exceptions, as this is only for cleanup. + * + * @param results + * the NamingEnumeration to close. + */ + private void closeNamingEnumeration(NamingEnumeration results) { + if (results != null) { + try { + results.close(); + } catch (Exception e) { + // Never mind this. + } + } + } + + private SearchControls getDefaultSearchControls(int searchScope, + boolean returningObjFlag, String[] attrs) { + + SearchControls controls = new SearchControls(); + controls.setSearchScope(searchScope); + controls.setReturningObjFlag(returningObjFlag); + controls.setReturningAttributes(attrs); + return controls; + } + + /** + * Make sure the returnObjFlag is set in the supplied SearchControls. Set it + * and log if it's not set. + * + * @param controls + * the SearchControls to check. + */ + private void assureReturnObjFlagSet(SearchControls controls) { + Validate.notNull(controls); + if (!controls.getReturningObjFlag()) { + log.info("The returnObjFlag of supplied SearchControls is not set" + + " but a ContextMapper is used - setting flag to true"); + controls.setReturningObjFlag(true); + } + } + + private final class NullDirContextProcessor implements DirContextProcessor { + public void postProcess(DirContext ctx) throws NamingException { + // Do nothing + } + + public void preProcess(DirContext ctx) throws NamingException { + // Do nothing + } + } + + /** + * A {@link NameClassPairCallbackHandler} that passes the NameClassPairs + * found to a NameClassPairMapper and collects the results in a list. + * + * @author Mattias Arthursson + */ + public class MappingCollectingNameClassPairCallbackHandler extends + CollectingNameClassPairCallbackHandler { + + private NameClassPairMapper mapper; + + public MappingCollectingNameClassPairCallbackHandler( + NameClassPairMapper mapper) { + this.mapper = mapper; + } + + /* + * @see org.springframework.ldap.CollectingNameClassPairCallbackHandler#getObjectFromNameClassPair(javax.naming.NameClassPair) + */ + public Object getObjectFromNameClassPair(NameClassPair nameClassPair) { + try { + return mapper.mapFromNameClassPair(nameClassPair); + } catch (javax.naming.NamingException e) { + throw LdapUtils.convertLdapException(e); + } + } + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.LdapOperations#lookupContext(javax.naming.Name) + */ + public DirContextOperations lookupContext(Name dn) { + return (DirContextOperations) lookup(dn); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.LdapOperations#lookupContext(java.lang.String) + */ + public DirContextOperations lookupContext(String dn) { + return (DirContextOperations) lookup(dn); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.LdapOperations#modifyAttributes(org.springframework.ldap.core.DirContextOperations) + */ + public void modifyAttributes(DirContextOperations ctx) { + Name dn = ctx.getDn(); + if (dn != null && ctx.isUpdateMode()) { + modifyAttributes(dn, ctx.getModificationItems()); + } else { + throw new IllegalStateException( + "The DirContextOperations instance needs to be properly initialized."); + } + + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/NameClassPairCallbackHandler.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/NameClassPairCallbackHandler.java new file mode 100644 index 00000000..5913c4de --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/NameClassPairCallbackHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.NameClassPair; + +/** + * Callback interface used by {@link LdapTemplate} search, list and listBindings + * methods. Implementations of this interface perform the actual work of + * extracting results from a single NameClassPair (a + * NameClassPair, Binding or + * SearchResult depending on the search operation) returned by an + * LDAP seach operation, such as search(), list(), and listBindings(). + * + * @author Mattias Arthursson + */ +public interface NameClassPairCallbackHandler { + /** + * Handle one entry. This method will be called once for each entry returned + * by a search or list. + * + * @param nameClassPair + * the NameClassPair returned from the + * NamingEnumeration. + */ + public void handleNameClassPair(NameClassPair nameClassPair); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/NameClassPairMapper.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/NameClassPairMapper.java new file mode 100644 index 00000000..8282368a --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/NameClassPairMapper.java @@ -0,0 +1,44 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.NameClassPair; +import javax.naming.NamingException; + +/** + * Responsible for mapping NameClassPair objects to beans. + * + * @author Mattias Arthursson + */ +public interface NameClassPairMapper { + /** + * Map NameClassPair to an Object. The supplied + * NameClassPair is one of the results from a search + * operation (search, list or listBindings). Depending on which search + * operation is being performed, the NameClassPair might be a + * SearchResult, Binding or + * NameClassPair. + * + * @param nameClassPair + * NameClassPair from a search operation. + * @return and Object built from the NameClassPair. + * @throws NamingException + * if one is encountered in the operation. + */ + public Object mapFromNameClassPair(NameClassPair nameClassPair) + throws NamingException; +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/ObjectRetrievalException.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/ObjectRetrievalException.java new file mode 100644 index 00000000..94a4235f --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/ObjectRetrievalException.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import org.springframework.ldap.NamingException; + +/** + * Thrown by a {@link ContextMapperCallbackHandler} when it cannot retrieve an + * object from the given Binding. + * + * @author Ulrik Sandberg + * @since 1.2 + */ +public class ObjectRetrievalException extends NamingException { + + /** + * Create a new ObjectRetrievalException. + * + * @param msg + * the detail message + * + */ + public ObjectRetrievalException(String msg) { + super(msg); + } + + /** + * Create a new ObjectRetrievalException. + * + * @param msg + * the detail message + * @param cause + * the root cause (if any) + */ + public ObjectRetrievalException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/SearchExecutor.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/SearchExecutor.java new file mode 100644 index 00000000..36f3d2d2 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/SearchExecutor.java @@ -0,0 +1,52 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +/** + * Interface for delegating an actual search operation. The typical + * implementation of executeSearch would be something like: + * + *

+ * SearchExecutor executor = new SearchExecutor(){
+ *   public NamingEnumeration executeSearch(DirContext ctx) throws NamingException{
+ *     return ctx.search(dn, filter, searchControls);
+ *   }
+ * }
+ * 
+ * + * @see org.springframework.ldap.core.LdapTemplate#search(SearchExecutor, + * NameClassPairCallbackHandler) + * + * @author Mattias Arthursson + */ +public interface SearchExecutor { + /** + * Execute the actual search. + * + * @param ctx + * the DirContext on which to work. + * @return the NamingEnumeration resulting from the search + * operation. + * @throws NamingException + * if the search results in one. + */ + public NamingEnumeration executeSearch(DirContext ctx) + throws NamingException; +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/core/package.html new file mode 100644 index 00000000..2cb85563 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/package.html @@ -0,0 +1,8 @@ + + + +Core package of the JNDI/LDAP support. +Provides a LdapTemplate class and various callback interfaces. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/AbstractContextMapper.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/AbstractContextMapper.java new file mode 100644 index 00000000..003a2438 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/AbstractContextMapper.java @@ -0,0 +1,58 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import org.springframework.ldap.core.ContextMapper; +import org.springframework.ldap.core.DirContextOperations; + +/** + * Abstract superclass that may be used instead of implementing + * {@link ContextMapper} directly. Subclassing from this superclass, the + * supplied context will be automatically cast to + * DirContextOperations. Note that if you use your own + * DirObjectFactory, this implementation will fail with a + * ClassCastException. + * + * @author Mattias Arthursson + * + */ +public abstract class AbstractContextMapper implements ContextMapper { + + /** + * {@inheritDoc} + * + * @throws ClassCastException + * if a custom DirObjectFactory implementation is + * used, causing the objects passed in be anything else than + * {@link DirContextOperations} instances. + */ + public final Object mapFromContext(Object ctx) { + return doMapFromContext((DirContextOperations) ctx); + } + + /** + * Map a single DirContextOperation to an object. The + * supplied instance is the object supplied to + * {@link #mapFromContext(Object)} cast to a + * DirContextOperations. + * + * @param ctx + * the context to map to an object. + * @return an object built from the data in the context. + */ + protected abstract Object doMapFromContext(DirContextOperations ctx); + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/AbstractContextSource.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/AbstractContextSource.java new file mode 100644 index 00000000..617fceda --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/AbstractContextSource.java @@ -0,0 +1,532 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import java.util.Hashtable; +import java.util.Map; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.JdkVersion; +import org.springframework.ldap.core.AuthenticationSource; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.support.LdapUtils; + +/** + * Abstract implementation of the {@link ContextSource} interface. By default, + * returns an authenticated + * DirContext implementation for both read-only and + * read-write operations. To have an anonymous environment created for read-only + * operations, set the anonymousReadOnly property to true. + *

+ * Implementing classes need to implement + * {@link #getDirContextInstance(Hashtable)} to create a DirContext instance of + * the desired type. + *

+ * If an {@link AuthenticationSource} is set, this will be used for getting user principal + * and password for each new connection, otherwise a default one will be created + * using the specified userDn and password. + *

+ * Note: When using implementations of this class outside of a Spring + * Context it is necessary to call {@link #afterPropertiesSet()} when all + * properties are set, in order to finish up initialization. + * + * @see org.springframework.ldap.core.LdapTemplate + * @see org.springframework.ldap.core.support.DefaultDirObjectFactory + * @see org.springframework.ldap.core.support.LdapContextSource + * @see org.springframework.ldap.core.support.DirContextSource + * + * @author Mattias Arthursson + * @author Adam Skogman + * @author Ulrik Sandberg + */ +public abstract class AbstractContextSource implements BaseLdapPathContextSource, InitializingBean { + + private static final Class DEFAULT_CONTEXT_FACTORY = com.sun.jndi.ldap.LdapCtxFactory.class; + + private static final Class DEFAULT_DIR_OBJECT_FACTORY = DefaultDirObjectFactory.class; + + private Class dirObjectFactory = DEFAULT_DIR_OBJECT_FACTORY; + + private Class contextFactory = DEFAULT_CONTEXT_FACTORY; + + private DistinguishedName base = DistinguishedName.EMPTY_PATH; + + protected String userDn = ""; + + protected String password = ""; + + private String[] urls; + + private boolean pooled = true; + + private Hashtable baseEnv = new Hashtable(); + + private Hashtable anonymousEnv; + + private AuthenticationSource authenticationSource; + + private boolean cacheEnvironmentProperties = true; + + private boolean anonymousReadOnly = false; + + private static final Log log = LogFactory.getLog(AbstractContextSource.class); + + public static final String SUN_LDAP_POOLING_FLAG = "com.sun.jndi.ldap.connect.pool"; + + private static final String JDK_142 = "1.4.2"; + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.ContextSource#getReadOnlyContext() + */ + public DirContext getReadOnlyContext() { + if (!anonymousReadOnly) { + return createContext(getAuthenticatedEnv()); + } + else { + return createContext(getAnonymousEnv()); + } + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.ContextSource#getReadWriteContext() + */ + public DirContext getReadWriteContext() { + return createContext(getAuthenticatedEnv()); + } + + /** + * Default implementation of setting the environment up to be authenticated. + * Override in subclass if necessary. + * + * @param env the environment to modify. + */ + protected void setupAuthenticatedEnvironment(Hashtable env) { + String principal = authenticationSource.getPrincipal(); + env.put(Context.SECURITY_PRINCIPAL, principal); + log.debug("Principal: '" + principal + "'"); + env.put(Context.SECURITY_CREDENTIALS, authenticationSource.getCredentials()); + } + + /** + * Close the context and swallow any exceptions. + * + * @param ctx the DirContext to close. + */ + private void closeContext(DirContext ctx) { + if (ctx != null) { + try { + ctx.close(); + } + catch (Exception e) { + } + } + } + + /** + * Assemble a valid url String from all registered urls to add as + * PROVIDER_URL to the environment. + * + * @param ldapUrls all individual url Strings. + * @return the full url String + */ + protected String assembleProviderUrlString(String[] ldapUrls) { + StringBuffer providerUrlBuffer = new StringBuffer(1024); + for (int i = 0; i < ldapUrls.length; i++) { + providerUrlBuffer.append(ldapUrls[i]); + if (!DistinguishedName.EMPTY_PATH.equals(base)) { + if (!ldapUrls[i].endsWith("/")) { + providerUrlBuffer.append("/"); + } + } + providerUrlBuffer.append(base.toUrl()); + providerUrlBuffer.append(' '); + } + return providerUrlBuffer.toString().trim(); + } + + /** + * Set the base suffix from which all operations should origin. If a base + * suffix is set, you will not have to (and, indeed, must not) specify the + * full distinguished names in any operations performed. + * + * @param base the base suffix. + */ + public void setBase(String base) { + this.base = new DistinguishedName(base); + } + + /** + * Get the base suffix from which all operations should originate. If a base + * suffix is set, you will not have to (and, indeed, must not) specify the + * full distinguished names in any operations performed. + * + * @return the base suffix + */ + protected DistinguishedName getBase() { + return base; + } + + /* + * (non-Javadoc) + * @see org.springframework.ldap.core.support.BaseLdapPathSource#getBaseLdapPath() + */ + public DistinguishedName getBaseLdapPath() { + return getBase().immutableDistinguishedName(); + } + + /* + * (non-Javadoc) + * @see org.springframework.ldap.core.support.BaseLdapPathSource#getBaseLdapPathAsString() + */ + public String getBaseLdapPathAsString() { + return getBaseLdapPath().toString(); + } + + /** + * Create a DirContext using the supplied environment. + * + * @param environment the Ldap environment to use when creating the + * DirContext. + * @return a new DirContext implpementation initialized with the supplied + * environment. + */ + protected DirContext createContext(Hashtable environment) { + DirContext ctx = null; + + try { + ctx = getDirContextInstance(environment); + + if (log.isInfoEnabled()) { + Hashtable ctxEnv = ctx.getEnvironment(); + String ldapUrl = (String) ctxEnv.get(Context.PROVIDER_URL); + log.debug("Got Ldap context on server '" + ldapUrl + "'"); + } + + return ctx; + } + catch (NamingException e) { + closeContext(ctx); + throw LdapUtils.convertLdapException(e); + } + } + + /** + * Set the context factory. Default is com.sun.jndi.ldap.LdapCtxFactory. + * + * @param contextFactory the context factory used when creating Contexts. + */ + public void setContextFactory(Class contextFactory) { + this.contextFactory = contextFactory; + } + + /** + * Get the context factory. + * + * @return the context factory used when creating Contexts. + */ + public Class getContextFactory() { + return contextFactory; + } + + /** + * Set the DirObjectFactory to use. Default is + * {@link DefaultDirObjectFactory}. The specified class needs to be an + * implementation of javax.naming.spi.DirObjectFactory. Note: Setting + * this value to null may have cause connection leaks when using + * ContextMapper methods in LdapTemplate. + * + * @param dirObjectFactory the DirObjectFactory to be used. Null means that + * no DirObjectFactory will be used. + */ + public void setDirObjectFactory(Class dirObjectFactory) { + this.dirObjectFactory = dirObjectFactory; + } + + /** + * Get the DirObjectFactory to use. + * + * @return the DirObjectFactory to be used. null means that + * no DirObjectFactory will be used. + */ + public Class getDirObjectFactory() { + return dirObjectFactory; + } + + /** + * Checks that all necessary data is set and that there is no compatibility + * issues, after which the instance is initialized. Note that you need to + * call this method explicitly after setting all desired properties if using + * the class outside of a Spring Context. + */ + public void afterPropertiesSet() throws Exception { + if (ArrayUtils.isEmpty(urls)) { + throw new IllegalArgumentException("At least one server url must be set"); + } + + if (!DistinguishedName.EMPTY_PATH.equals(base) && getJdkVersion().compareTo(JDK_142) < 0) { + throw new IllegalArgumentException("Base path is not supported for JDK versions < 1.4.2"); + } + + if (authenticationSource == null) { + log.debug("AuthenticationSource not set - " + "using default implementation"); + if (StringUtils.isBlank(userDn)) { + log.info("Property 'userDn' not set - " + "anonymous context will be used for read-write operations"); + } + else if (StringUtils.isBlank(password)) { + log.info("Property 'password' not set - " + "blank password will be used"); + } + authenticationSource = new SimpleAuthenticationSource(); + } + + if (cacheEnvironmentProperties) { + anonymousEnv = setupAnonymousEnv(); + } + } + + private Hashtable setupAnonymousEnv() { + if (pooled) { + baseEnv.put(SUN_LDAP_POOLING_FLAG, "true"); + log.debug("Using LDAP pooling."); + } + else { + baseEnv.remove(SUN_LDAP_POOLING_FLAG); + log.debug("Not using LDAP pooling"); + } + + Hashtable env = new Hashtable(baseEnv); + + env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory.getName()); + env.put(Context.PROVIDER_URL, assembleProviderUrlString(urls)); + + if (dirObjectFactory != null) { + env.put(Context.OBJECT_FACTORIES, dirObjectFactory.getName()); + } + + if (!DistinguishedName.EMPTY_PATH.equals(base)) { + // Save the base path for use in the DefaultDirObjectFactory. + env.put(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY, base); + } + + log.debug("Trying provider Urls: " + assembleProviderUrlString(urls)); + + return env; + } + + /** + * Set the password (credentials) to use for getting authenticated contexts. + * + * @param password the password. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Set the user distinguished name (principal) to use for getting + * authenticated contexts. + * + * @param userDn the user distinguished name. + */ + public void setUserDn(String userDn) { + this.userDn = userDn; + } + + /** + * Set the user distinguished name (principal) to use for getting + * authenticated contexts. + * + * @param userName the user distinguished name. + * @deprecated Use {@link #setUserDn(String)} instead. + */ + public void setUserName(String userName) { + setUserDn(userName); + } + + /** + * Set the urls of the LDAP servers. Use this method if several servers are + * required. + * + * @param urls the urls of all servers. + */ + public void setUrls(String[] urls) { + this.urls = urls; + } + + /** + * Get the urls of the LDAP servers. + * + * @return the urls of all servers. + */ + public String[] getUrls() { + return urls; + } + + /** + * Set the url of the LDAP server. Utility method if only one server is + * used. + * + * @param url the url of the LDAP server. + */ + public void setUrl(String url) { + this.urls = new String[] { url }; + } + + /** + * Set whether the pooling flag should be set. Default is true. Note that + * since LDAP pooling is system wide, full configuration of this needs be + * done using system parameters as specified in the LDAP/JNDI documentation. + * Also note, that pooling is done on user dn basis, i.e. each individually + * authenticated connection will be pooled separately. This means that LDAP + * pooling will be most efficient using anonymous connections or connections + * authenticated using one single system user. + * + * @param pooled whether Contexts should be pooled. + */ + public void setPooled(boolean pooled) { + this.pooled = pooled; + } + + /** + * Get whether the pooling flag should be set. + * + * @return whether Contexts should be pooled. + */ + public boolean isPooled() { + return pooled; + } + + /** + * If any custom environment properties are needed, these can be set using + * this method. + * + * @param baseEnvironmentProperties + */ + public void setBaseEnvironmentProperties(Map baseEnvironmentProperties) { + this.baseEnv = new Hashtable(baseEnvironmentProperties); + } + + String getJdkVersion() { + return JdkVersion.getJavaVersion(); + } + + protected Hashtable getAnonymousEnv() { + if (cacheEnvironmentProperties) { + return anonymousEnv; + } + else { + return setupAnonymousEnv(); + } + } + + protected Hashtable getAuthenticatedEnv() { + // The authenticated environment should always be rebuilt. + Hashtable env = new Hashtable(getAnonymousEnv()); + setupAuthenticatedEnvironment(env); + return env; + } + + /** + * Set the authentication source to use when retrieving user principal and + * credentials. + * + * @param authenticationSource the {@link AuthenticationSource} that will + * provide user info. + */ + public void setAuthenticationSource(AuthenticationSource authenticationSource) { + this.authenticationSource = authenticationSource; + } + + /** + * Get the authentication source. + * + * @return the {@link AuthenticationSource} that will provide user info. + */ + public AuthenticationSource getAuthenticationSource() { + return authenticationSource; + } + + /** + * Set whether environment properties should be cached between requsts for + * anonymous environment. Default is true; setting this + * property to false causes the environment Hashmap to be + * rebuilt from the current property settings of this instance between each + * request for an anonymous environment. + * + * @param cacheEnvironmentProperties true causes that the + * anonymous environment properties should be cached, false + * causes the Hashmap to be rebuilt for each request. + */ + public void setCacheEnvironmentProperties(boolean cacheEnvironmentProperties) { + this.cacheEnvironmentProperties = cacheEnvironmentProperties; + } + + /** + * Set whether an anonymous environment should be used for read-only + * operations. Default is false. + * + * @param anonymousReadOnly true if an anonymous environment + * should be used for read-only operations, false otherwise. + */ + public void setAnonymousReadOnly(boolean anonymousReadOnly) { + this.anonymousReadOnly = anonymousReadOnly; + } + + /** + * Get whether an anonymous environment should be used for read-only + * operations. + * + * @return true if an anonymous environment should be used + * for read-only operations, false otherwise. + */ + public boolean isAnonymousReadOnly() { + return anonymousReadOnly; + } + + /** + * Implement in subclass to create a DirContext of the desired type (e.g. + * InitialDirContext or InitialLdapContext). + * + * @param environment the environment to use when creating the instance. + * @return a new DirContext instance. + * @throws NamingException if one is encountered when creating the instance. + */ + protected abstract DirContext getDirContextInstance(Hashtable environment) throws NamingException; + + class SimpleAuthenticationSource implements AuthenticationSource { + + public String getPrincipal() { + return userDn; + } + + public String getCredentials() { + return password; + } + + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/AggregateDirContextProcessor.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/AggregateDirContextProcessor.java new file mode 100644 index 00000000..5f4cd33d --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/AggregateDirContextProcessor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core.support; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import org.springframework.ldap.core.DirContextProcessor; + +/** + * Manages a sequence of {@link DirContextProcessor} instances. Applies + * {@link #preProcess(DirContext)} and {@link #postProcess(DirContext)} + * respectively in sequence on the managed objects. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public class AggregateDirContextProcessor implements DirContextProcessor { + + private List dirContextProcessors = new LinkedList(); + + /** + * Add the supplied DirContextProcessor to the list of managed objects. + * + * @param processor + * the DirContextpProcessor to add. + */ + public void addDirContextProcessor(DirContextProcessor processor) { + dirContextProcessors.add(processor); + } + + /** + * Get the list of managed {@link DirContextProcessor} instances. + * + * @return the managed list of {@link DirContextProcessor} instances. + */ + public List getDirContextProcessors() { + return dirContextProcessors; + } + + /** + * Set the list of managed {@link DirContextProcessor} instances. + * + * @param dirContextProcessors + * the list of {@link DirContextProcessor} instances to set. + */ + public void setDirContextProcessors(List dirContextProcessors) { + this.dirContextProcessors = dirContextProcessors; + } + + /* + * @see org.springframework.ldap.core.DirContextProcessor#preProcess(javax.naming.directory.DirContext) + */ + public void preProcess(DirContext ctx) throws NamingException { + for (Iterator iter = dirContextProcessors.iterator(); iter.hasNext();) { + DirContextProcessor processor = (DirContextProcessor) iter.next(); + processor.preProcess(ctx); + } + } + + /* + * @see org.springframework.ldap.core.DirContextProcessor#postProcess(javax.naming.directory.DirContext) + */ + public void postProcess(DirContext ctx) throws NamingException { + for (Iterator iter = dirContextProcessors.iterator(); iter.hasNext();) { + DirContextProcessor processor = (DirContextProcessor) iter.next(); + processor.postProcess(ctx); + } + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathAware.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathAware.java new file mode 100644 index 00000000..45ebf5de --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathAware.java @@ -0,0 +1,49 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import org.springframework.ldap.core.DistinguishedName; + +/** + * Interface to be implemented by classes that want to have access to the base + * context used in the active ContextSource. There are several + * cases in which services may want to have access to the base context, e.g. + * when working with groups (groupOfNames objectclass), in which + * case the full DN of each group member needs to be specified in the attribute + * value. + *

+ * If a class implements this interface and a + * {@link BaseLdapPathBeanPostProcessor} is defined in the + * ApplicationContext, the default base path will automatically + * passed to the {@link #setBaseLdapPath(DistinguishedName)} method on + * initialization. + *

+ * NB:The ContextSource needs to be a subclass of + * {@link AbstractContextSource} for this mechanism to work. + * + * + * @author Mattias Arthursson + * @since 1.2 + */ +public interface BaseLdapPathAware { + + /** + * Set the base LDAP path specified in the current + * ApplicationContext. + * @param baseLdapPath the base path used in the ContextSource + */ + public void setBaseLdapPath(DistinguishedName baseLdapPath); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostProcessor.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostProcessor.java new file mode 100644 index 00000000..d7037e60 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostProcessor.java @@ -0,0 +1,126 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.util.StringUtils; + +/** + * This BeanPostProcessor checks each bean if it implements + * {@link BaseLdapPathAware}. If it does, the default context base LDAP path + * will be determined, and that value will be injected to the + * {@link BaseLdapPathAware#setBaseLdapPath(DistinguishedName)} method of the + * processed bean. + *

+ * If the baseLdapPath property of this + * BeanPostProcessor is set, that value will be used. Otherwise, + * in order to determine which base LDAP path to supply to the instance the + * ApplicationContext is searched for any beans that are + * implementations of {@link BaseLdapPathSource}. If one single occurrence + * is found, that instance is queried for its base path, and that is what will + * be injected. If more than one {@link BaseLdapPathSource} instance is + * configured in the ApplicationContext, the name of the one to + * use will need to be specified to the baseLdapPathSourceName + * property; otherwise the post processing will fail. If no + * {@link BaseLdapPathSource} implementing bean is found in the context and + * the basePath property is not set, post processing will also + * fail. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class BaseLdapPathBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware { + + private ApplicationContext applicationContext; + + private DistinguishedName basePath; + + private String baseLdapPathSourceName; + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof BaseLdapPathAware) { + BaseLdapPathAware baseLdapPathAware = (BaseLdapPathAware) bean; + + if (basePath != null) { + baseLdapPathAware.setBaseLdapPath(basePath); + } + else { + BaseLdapPathSource ldapPathSource = getBaseLdapPathSourceFromApplicationContext(); + baseLdapPathAware.setBaseLdapPath(ldapPathSource.getBaseLdapPath()); + } + } + return bean; + } + + BaseLdapPathSource getBaseLdapPathSourceFromApplicationContext() { + if (StringUtils.hasLength(baseLdapPathSourceName)) { + return (BaseLdapPathSource) applicationContext.getBean(baseLdapPathSourceName); + } + String[] definedContextSources = applicationContext.getBeanNamesForType(BaseLdapPathSource.class); + if (definedContextSources.length < 1) { + throw new NoSuchBeanDefinitionException("No BaseLdapPathSource implementation definition found"); + } + else if (definedContextSources.length > 1) { + throw new NoSuchBeanDefinitionException( + "More than BaseLdapPathSource implementation definition found in current ApplicationContext"); + } + return (BaseLdapPathSource) applicationContext.getBean(definedContextSources[0]); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, + * java.lang.String) + */ + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + // Do nothing for this implementation + return bean; + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + /** + * Set the base path to be injected in all {@link BaseLdapPathAware} beans. + * If this property is not set, the default base path will be determined + * from any defined {@link BaseLdapPathSource} instances available in the + * ApplicationContext. + * + * @param basePath the base path. + */ + public void setBasePath(DistinguishedName basePath) { + this.basePath = basePath; + } + + /** + * Set the name of the ContextSource bean to use for getting + * the base path. This method is typically useful if several ContextSource + * instances have been configured. + * + * @param contextSourceName the name of the ContextSource + * bean to use for determining the base path. + */ + public void setBaseLdapPathSourceName(String contextSourceName) { + this.baseLdapPathSourceName = contextSourceName; + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathContextSource.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathContextSource.java new file mode 100644 index 00000000..f0d607bd --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathContextSource.java @@ -0,0 +1,28 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import org.springframework.ldap.core.ContextSource; + +/** + * Interface to be implemented by ContextSources that are capable + * of providing the base LDAP path. + * + * @author Mattias Arthursson + */ +public interface BaseLdapPathContextSource extends ContextSource, BaseLdapPathSource { + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathSource.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathSource.java new file mode 100644 index 00000000..c26a914e --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/BaseLdapPathSource.java @@ -0,0 +1,46 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.DistinguishedName; + +/** + * Implementations of this interface are capable of providing a base LDAP path. + * The base LDAP path is the root path to which all LDAP operations performed on + * a particular context are relative. + * + * @see ContextSource + * + * @author Mattias Arthursson + */ +public interface BaseLdapPathSource { + /** + * Get the base LDAP path as a {@link DistinguishedName}. + * + * @return the base LDAP path as a {@link DistinguishedName}. The path will + * be empty if no base path is specified. + */ + DistinguishedName getBaseLdapPath(); + + /** + * Get the base LDAP path as a String. + * + * @return the base LDAP path as a An empty String will be returned if no + * base path is specified. + */ + String getBaseLdapPathAsString(); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/CountNameClassPairCallbackHandler.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/CountNameClassPairCallbackHandler.java new file mode 100644 index 00000000..ce51601a --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/CountNameClassPairCallbackHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import javax.naming.NameClassPair; + +import org.springframework.ldap.core.NameClassPairCallbackHandler; + +/** + * A {@link NameClassPairCallbackHandler} for counting all returned entries. + * + * @author Mattias Arthursson + * + */ +public class CountNameClassPairCallbackHandler implements + NameClassPairCallbackHandler { + + private int noOfRows = 0; + + /** + * Get the number of rows that was returned by the search. + * + * @return the number of entries that have been handled. + */ + public int getNoOfRows() { + return noOfRows; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.SearchResultCallbackHandler#handleSearchResult(javax.naming.directory.SearchResult) + */ + public void handleNameClassPair(NameClassPair nameClassPair) { + noOfRows++; + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/DefaultDirObjectFactory.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/DefaultDirObjectFactory.java new file mode 100644 index 00000000..5f823c92 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/DefaultDirObjectFactory.java @@ -0,0 +1,97 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core.support; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.directory.Attributes; +import javax.naming.spi.DirObjectFactory; + +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; + +/** + * Default implementation of the DirObjectFactory interface. Creates a + * {@link DirContextAdapter} from the supplied arguments. + * + * @author Mattias Arthursson + */ +public class DefaultDirObjectFactory implements DirObjectFactory { + /** + * Key to use in the ContextSource implementation to store the value of the + * base path suffix, if any, in the Ldap Environment. + * + * @deprecated Use {@link BaseLdapPathAware} and {@link BaseLdapPathBeanPostProcessor} instead. + */ + public static final String JNDI_ENV_BASE_PATH_KEY = "org.springframework.ldap.base.path"; + + /* + * (non-Javadoc) + * + * @see javax.naming.spi.DirObjectFactory#getObjectInstance(java.lang.Object, + * javax.naming.Name, javax.naming.Context, java.util.Hashtable, + * javax.naming.directory.Attributes) + */ + public Object getObjectInstance(Object obj, Name name, Context nameCtx, + Hashtable environment, Attributes attrs) throws Exception { + + try { + String nameInNamespace = null; + if (nameCtx != null) { + nameInNamespace = nameCtx.getNameInNamespace(); + } else { + nameInNamespace = ""; + } + DirContextAdapter dirContextAdapter = new DirContextAdapter(attrs, + name, new DistinguishedName(nameInNamespace)); + dirContextAdapter.setUpdateMode(true); + + return dirContextAdapter; + } finally { + // It seems that the object supplied to the obj parameter is a + // DirContext instance with reference to the same Ldap connection as + // the original context. Since it is not the same instance (that's + // the nameCtx parameter) this one really needs to be closed in + // order to correctly clean up and return the connection to the pool + // when we're finished with the surrounding operation. + if (obj instanceof Context) { + + Context ctx = (Context) obj; + try { + ctx.close(); + } catch (Exception e) { + // Never mind this + } + + } + } + } + + /* + * (non-Javadoc) + * + * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, + * javax.naming.Name, javax.naming.Context, java.util.Hashtable) + */ + public Object getObjectInstance(Object obj, Name name, Context nameCtx, + Hashtable environment) throws Exception { + return null; + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/DirContextSource.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/DirContextSource.java new file mode 100644 index 00000000..8f973b26 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/DirContextSource.java @@ -0,0 +1,48 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core.support; + +import java.util.Hashtable; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + + +/** + * ContextSource implementation which creates InitialDirContext instances, for + * LDAPv2 compatibility. For configuration information, see + * {@link org.springframework.ldap.core.support.AbstractContextSource AbstractContextSource}. + * + * @see org.springframework.ldap.core.support.AbstractContextSource + * + * @author Mattias Arthursson + */ +public class DirContextSource extends AbstractContextSource { + + /** + * Create a new InitialDirContext instance. + * + * @param environment + * the environment to use when creating the context. + * @return a new InitialDirContext implementation. + */ + protected DirContext getDirContextInstance(Hashtable environment) + throws NamingException { + return new InitialDirContext(environment); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/LdapContextSource.java b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/LdapContextSource.java new file mode 100644 index 00000000..df1f6760 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/LdapContextSource.java @@ -0,0 +1,45 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core.support; + +import java.util.Hashtable; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.InitialLdapContext; + +/** + * ContextSource implementation which creates an InitialLdapContext + * instance. For configuration information, see + * {@link org.springframework.ldap.core.support.AbstractContextSource AbstractContextSource}. + * + * @see org.springframework.ldap.core.support.AbstractContextSource + * + * @author Mattias Arthursson + * @author Adam Skogman + * @author Ulrik Sandberg + */ +public class LdapContextSource extends AbstractContextSource { + + /* + * @see org.springframework.ldap.support.AbstractContextSource#getDirContextInstance(java.util.Hashtable) + */ + protected DirContext getDirContextInstance(Hashtable environment) + throws NamingException { + return new InitialLdapContext(environment, null); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/core/support/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/package.html new file mode 100644 index 00000000..30162db2 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/core/support/package.html @@ -0,0 +1,7 @@ + + + +Support classes the core Spring LDAP package. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/AbstractFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/AbstractFilter.java new file mode 100644 index 00000000..889abef4 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/AbstractFilter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +/** + * Convenience class that implements most of the methods in the Filter + * interface. + * + * @author Adam Skogman + */ +public abstract class AbstractFilter implements Filter { + + protected AbstractFilter() { + super(); + } + + /* + * @see org.springframework.ldap.filter.Filter#encode(java.lang.StringBuffer) + */ + public abstract StringBuffer encode(StringBuffer buff); + + /* + * @see org.springframework.ldap.filter.Filter#encode() + */ + public String encode() { + StringBuffer buf = new StringBuffer(256); + buf = encode(buf); + return buf.toString(); + } + + /* + * @see java.lang.Object#toString() + */ + public String toString() { + return encode(); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/AndFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/AndFilter.java new file mode 100644 index 00000000..222a049e --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/AndFilter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +/** + * A filter for a logical AND. Example: + * + *

+ *     AndFilter filter = new AndFilter();
+ *     filter.and(new EqualsFilter("objectclass", "person");
+ *     filter.and(new EqualsFilter("cn", "Some CN");
+ *     System.out.println(filter.ecode());    
+ * 
+ * + * would result in: (&(objectclass=person)(cn=Some CN)) + * + * @see org.springframework.ldap.filter.EqualsFilter + * @author Adam Skogman + * @author Mattias Arthursson + */ +public class AndFilter extends BinaryLogicalFilter { + + private static final String AMPERSAND = "&"; + + /* + * @see org.springframework.ldap.filter.BinaryLogicalFilter#getLogicalOperator() + */ + protected String getLogicalOperator() { + return AMPERSAND; + } + + /** + * Add a query to the AND expression. + * + * @param query The expression to AND with the rest of the AND:ed + * expressions. + * @return This LdapAndQuery + */ + public AndFilter and(Filter query) { + queryList.add(query); + return this; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/BinaryLogicalFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/BinaryLogicalFilter.java new file mode 100644 index 00000000..7b10f706 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/BinaryLogicalFilter.java @@ -0,0 +1,97 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +/** + * Abstract superclass for binary logical operations, that is "AND" + * and "OR" operations. + * + * @author Mattias Arthursson + */ +public abstract class BinaryLogicalFilter extends AbstractFilter { + + protected List queryList = new LinkedList(); + + /* + * @see org.springframework.ldap.filter.AbstractFilter#encode(java.lang.StringBuffer) + */ + public StringBuffer encode(StringBuffer buff) { + if (queryList.size() <= 0) { + + // only output query if contains anything + return buff; + + } + else if (queryList.size() == 1) { + + // don't add the & + Filter query = (Filter) queryList.get(0); + return query.encode(buff); + + } + else { + buff.append("(" + getLogicalOperator()); + + for (Iterator i = queryList.iterator(); i.hasNext();) { + Filter query = (Filter) i.next(); + buff = query.encode(buff); + } + + buff.append(")"); + + return buff; + } + } + + /** + * Implement this in subclass to return the logical operator, for example + * &qout;&&qout;. + * + * @return the logical operator. + */ + protected abstract String getLogicalOperator(); + + /** + * Compares each filter in turn. + * + * @see org.springframework.ldap.filter.Filter#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj instanceof BinaryLogicalFilter && this.getClass() == obj.getClass()) { + return EqualsBuilder.reflectionEquals(this, obj); + } + else { + return false; + } + } + + /** + * Hashes all contained data. + * + * @see org.springframework.ldap.filter.Filter#hashCode() + */ + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/CompareFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/CompareFilter.java new file mode 100644 index 00000000..1529d4c3 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/CompareFilter.java @@ -0,0 +1,123 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.springframework.ldap.core.LdapEncoder; + +/** + * Abstract superclass for filters that compare values. + * + * @author Mattias Arthursson + */ +public abstract class CompareFilter extends AbstractFilter { + + private final String attribute; + + private final String value; + + private final String encodedValue; + + public CompareFilter(String attribute, String value) { + this.attribute = attribute; + this.value = value; + this.encodedValue = encodeValue(value); + } + + /** + * For testing purposes. + * + * @return the encoded value. + */ + String getEncodedValue() { + return encodedValue; + } + + /** + * Override to perform special encoding in subclass. + * + * @param value the value to encode. + * @return properly escaped value. + */ + protected String encodeValue(String value) { + return LdapEncoder.filterEncode(value); + } + + /** + * Convenience constructor for int values. + * + * @param attribute Name of attribute in filter. + * @param value The value of the attribute in the filter. + */ + public CompareFilter(String attribute, int value) { + this.attribute = attribute; + this.value = String.valueOf(value); + this.encodedValue = LdapEncoder.filterEncode(this.value); + } + + /* + * @see org.springframework.ldap.filter.AbstractFilter#encode(java.lang.StringBuffer) + */ + public StringBuffer encode(StringBuffer buff) { + buff.append('('); + buff.append(attribute).append(getCompareString()).append(encodedValue); + buff.append(')'); + + return buff; + } + + /** + * Compares key and value before encoding. + * + * @see org.springframework.ldap.filter.Filter#equals(java.lang.Object) + */ + public boolean equals(Object o) { + if (o instanceof CompareFilter && o.getClass() == this.getClass()) { + CompareFilter that = (CompareFilter) o; + EqualsBuilder builder = new EqualsBuilder(); + builder.append(this.attribute, that.attribute); + builder.append(this.value, that.value); + return builder.isEquals(); + } + else { + return false; + } + } + + /** + * Calculate the hash code for the attribute and the value. + * + * @see org.springframework.ldap.filter.Filter#hashCode() + */ + public int hashCode() { + HashCodeBuilder builder = new HashCodeBuilder(); + builder.append(attribute); + builder.append(value); + return builder.toHashCode(); + } + + /** + * Implement this method in subclass to return a String representing the + * operator. The {@link EqualsFilter#getCompareString()} would for example + * return an equals sign, "=". + * + * @return the String to use as operator in the comparison for the specific + * subclass. + */ + protected abstract String getCompareString(); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/EqualsFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/EqualsFilter.java new file mode 100644 index 00000000..e452be35 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/EqualsFilter.java @@ -0,0 +1,59 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +/** + * A filter for 'equals'. The following code: + * + *
+ * EqualsFilter filter = new EqualsFilter("cn", "Some CN");
+ * System.out.println(filter.ecode());
+ * 
+ * + * would result in: + * + *
+ * (cn=Some CN)
+ * 
+ * + * @author Adam Skogman + */ +public class EqualsFilter extends CompareFilter { + + private static final String EQUALS_SIGN = "="; + + public EqualsFilter(String attribute, String value) { + super(attribute, value); + } + + /** + * Convenience constructor for int values. + * + * @param attribute Name of attribute in filter. + * @param value The value of the attribute in the filter. + */ + public EqualsFilter(String attribute, int value) { + super(attribute, value); + } + + /* + * @see org.springframework.ldap.filter.CompareFilter#getCompareString() + */ + protected String getCompareString() { + return EQUALS_SIGN; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/Filter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/Filter.java new file mode 100644 index 00000000..06cb9c8e --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/Filter.java @@ -0,0 +1,59 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +/** + * Common interface for LDAP filters. + * + * @author Adam Skogman + * @see RFC 1960: A String Representation of LDAP Search Filters + */ +public interface Filter { + + /** + * Encodes the filter to a String. + * + * @return The encoded filter in the standard String format + */ + public String encode(); + + /** + * Encodes the filter to a StringBuffer. + * + * @param buf + * The StringBuffer to encode the filter to + * @return The same StringBuffer as was given + */ + public StringBuffer encode(StringBuffer buf); + + /** + * All filters must implement equals. + * + * @param o + * @return true if the objects are equal. + */ + public boolean equals(Object o); + + /** + * All filters must implement hashCode. + * + * @return the hash code according to the contract in + * {@link Object#hashCode()} + */ + public int hashCode(); + +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/GreaterThanOrEqualsFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/GreaterThanOrEqualsFilter.java new file mode 100644 index 00000000..c46be2ac --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/GreaterThanOrEqualsFilter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +/** + * A filter to compare >=. LDAP RFC does not allow > comparison. The following + * code: + * + *
+ * GreaterThanOrEqualsFilter filter = new GreaterThanOrEqualsFilter("cn", "Some CN");
+ * System.out.println(filter.ecode());
+ * 
+ * + * would result in: + * + *
+ * (cn>=Some CN)
+ * 
+ * + * @author Mattias Arthursson + */ +public class GreaterThanOrEqualsFilter extends CompareFilter { + + private static final String GREATER_THAN_OR_EQUALS = ">="; + + public GreaterThanOrEqualsFilter(String attribute, String value) { + super(attribute, value); + } + + public GreaterThanOrEqualsFilter(String attribute, int value) { + super(attribute, value); + } + + /* + * @see org.springframework.ldap.filter.CompareFilter#getCompareString() + */ + protected String getCompareString() { + return GREATER_THAN_OR_EQUALS; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/LessThanOrEqualsFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/LessThanOrEqualsFilter.java new file mode 100644 index 00000000..389a654b --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/LessThanOrEqualsFilter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +/** + * A filter to compare <=. LDAP RFC does not allow < comparison. The following + * code: + * + *
+ * LessThanOrEqualsFilter filter = new LessThanOrEqualsFilter("cn", "Some CN");
+ * System.out.println(filter.ecode());
+ * 
+ * + * would result in: + * + *
+ * (cn<=Some CN)
+ * 
+ * + * @author Mattias Arthursson + */ +public class LessThanOrEqualsFilter extends CompareFilter { + + private static final String LESS_THAN_OR_EQUALS = "<="; + + public LessThanOrEqualsFilter(String attribute, String value) { + super(attribute, value); + } + + public LessThanOrEqualsFilter(String attribute, int value) { + super(attribute, value); + } + + /* + * @see org.springframework.ldap.filter.CompareFilter#getCompareString() + */ + protected String getCompareString() { + return LESS_THAN_OR_EQUALS; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/LikeFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/LikeFilter.java new file mode 100644 index 00000000..5ad694e9 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/LikeFilter.java @@ -0,0 +1,75 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.springframework.ldap.core.LdapEncoder; + +/** + * This filter allows the user to specify wildcards (*) by not escaping them in + * the filter. The following code: + * + *
+ * LikeFilter filter = new LikeFilter("cn", "foo*");
+ * System.out.println(filter.ecode());
+ * 
+ * + * would result in: + * + *
+ *  (cn=foo*)
+ * 
+ * + * @author Anders Henja + * @author Mattias Arthursson + */ +public class LikeFilter extends EqualsFilter { + + public LikeFilter(String attribute, String value) { + super(attribute, value); + } + + /* + * @see org.springframework.ldap.filter.CompareFilter#encodeValue(java.lang.String) + */ + protected String encodeValue(String value) { + // just return if blank string + if (value == null) { + return ""; + } + + String[] substrings = value.split("\\*", -2); + + if (substrings.length == 1) { + return LdapEncoder.filterEncode(substrings[0]); + } + + StringBuffer buff = new StringBuffer(); + for (int i = 0; i < substrings.length; i++) { + buff.append(LdapEncoder.filterEncode(substrings[i])); + if (i < substrings.length - 1) { + buff.append("*"); + } + else { + if (substrings[i].equals("")) { + continue; + } + } + } + + return buff.toString(); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/NotFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/NotFilter.java new file mode 100644 index 00000000..8584ad00 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/NotFilter.java @@ -0,0 +1,84 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.apache.commons.lang.Validate; + +/** + * A filter for 'not'. The following code: + * + *
+ * Filter filter = new NotFilter(new EqualsFilter("cn", "foo");
+ * System.out.println(filter.encode());
+ * 
+ * + * would result in: + * + *
+ * (!(cn = foo))
+ * 
+ * + * @author Adam Skogman + */ +public class NotFilter extends AbstractFilter { + + private final Filter filter; + + static private final int HASH = "!".hashCode(); + + /** + * Create a filter that negates the outcome of the given filter. + * + * @param filter The filter that should be negated. + */ + public NotFilter(Filter filter) { + Validate.notNull(filter); + this.filter = filter; + } + + /* + * @see org.springframework.ldap.filter.AbstractFilter#encode(java.lang.StringBuffer) + */ + public StringBuffer encode(StringBuffer buff) { + + buff.append("(!"); + filter.encode(buff); + buff.append(')'); + + return buff; + } + + /* + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) { + + if (o instanceof NotFilter && o.getClass() == this.getClass()) { + NotFilter f = (NotFilter) o; + return this.filter.equals(f.filter); + } + + return false; + } + + /* + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return HASH ^ filter.hashCode(); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/OrFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/OrFilter.java new file mode 100644 index 00000000..b0369a70 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/OrFilter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +/** + * Filter for logical OR. + * + *
+ *        AndFilter filter = new AndFilter();
+ *        filter.or(new EqualsFilter("objectclass", "person");
+ *        filter.or(new EqualsFilter("objectclass", "organizationalUnit");
+ *        System.out.println(filter.ecode());    
+ * 
+ * + * would result in: + * (|(objectclass=person)(objectclass=organizationalUnit)) + * + * @author Adam Skogman + * @author Mattias Arthursson + */ +public class OrFilter extends BinaryLogicalFilter { + + private static final String PIPE_SIGN = "|"; + + /** + * Add a query to the OR expression + * + * @param query The query to or with the rest of the or:ed queries. + * @return This LdapOrQuery + */ + public OrFilter or(Filter query) { + queryList.add(query); + return this; + } + + /* + * @see org.springframework.ldap.filter.BinaryLogicalFilter#getLogicalOperator() + */ + protected String getLogicalOperator() { + return PIPE_SIGN; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/WhitespaceWildcardsFilter.java b/mvn-build/core/src/main/java/org/springframework/ldap/filter/WhitespaceWildcardsFilter.java new file mode 100644 index 00000000..73c02f8c --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/WhitespaceWildcardsFilter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang.StringUtils; +import org.springframework.ldap.core.LdapEncoder; + +/** + * This filter automatically converts all whitespace to wildcards (*). The + * following code: + * + *
+ * WhitespaceWildcardsFilter filter = new WhitespaceWildcardsFilter("cn", "Some CN");
+ * System.out.println(filter.ecode());
+ * 
+ * + * would result in: (cn=*Some*CN*) + * + * @author Adam Skogman + * @author Mattias Arthursson + */ +public class WhitespaceWildcardsFilter extends EqualsFilter { + private static Pattern starReplacePattern = Pattern.compile("\\s+"); + + public WhitespaceWildcardsFilter(String attribute, String value) { + super(attribute, value); + } + + /* + * @see org.springframework.ldap.filter.CompareFilter#encodeValue(java.lang.String) + */ + protected String encodeValue(String value) { + + // blank string means just ONE star + if (StringUtils.isBlank(value)) { + return "*"; + } + + // trim value, we will add in stars first and last anywhay + value = value.trim(); + + // filter encode so that any stars etc. are preserved + String filterEncoded = LdapEncoder.filterEncode(value); + + // Now replace all whitespace with stars + Matcher m = starReplacePattern.matcher(filterEncoded); + + // possibly 2 longer (stars at ends) + StringBuffer buff = new StringBuffer(value.length() + 2); + + buff.append('*'); + + while (m.find()) { + m.appendReplacement(buff, "*"); + } + m.appendTail(buff); + + buff.append('*'); + + return buff.toString(); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/filter/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/filter/package.html new file mode 100644 index 00000000..5b26891b --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/filter/package.html @@ -0,0 +1,20 @@ + + + +Utility classes for dynamically building LDAP +filters. Filters can be nested and wrapped around each other: +
+AndFilter andFilter = new AndFilter();
+andFilter.and(new EqualsFilter("objectclass", "person");
+andFilter.and(new EqualsFilter("cn", "Some CN");
+OrFilter orFilter = new OrFilter();
+orFilter.or(andFilter);
+orFilter.or(new EqualsFilter("objectclass", "organizationalUnit));
+System.out.println(orFilter.encode());
+
+would result in: + +
(|(&(objectclass=person)(cn=Some CN))(objectclass=organizationalUnit))
+ + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/package.html new file mode 100644 index 00000000..f3059be9 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/package.html @@ -0,0 +1,7 @@ + + + +Base package of Spring LDAP, containing an unchecked mirror of the JNDI NamingException hierarchy. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingContext.java b/mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingContext.java new file mode 100644 index 00000000..1d51315e --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingContext.java @@ -0,0 +1,381 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +import org.apache.commons.lang.Validate; +import org.apache.commons.pool.KeyedObjectPool; +import org.springframework.ldap.pool.factory.PoolingContextSource; + +/** + * Used by {@link PoolingContextSource} to wrap a {@link Context}, delegating most methods + * to the underlying context, retains a reference to the pool the context was checked out + * from and returns itself to the pool when {@link #close()} is called. + * + * @author Eric Dalquist + */ +public class DelegatingContext implements Context { + private KeyedObjectPool keyedObjectPool; + private Context delegateContext; + private final DirContextType dirContextType; + + + /** + * Create a new delegating context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateContext The context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingContext(KeyedObjectPool keyedObjectPool, Context delegateContext, DirContextType dirContextType) { + Validate.notNull(keyedObjectPool, "keyedObjectPool may not be null"); + Validate.notNull(delegateContext, "delegateContext may not be null"); + Validate.notNull(dirContextType, "dirContextType may not be null"); + + this.keyedObjectPool = keyedObjectPool; + this.delegateContext = delegateContext; + this.dirContextType = dirContextType; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this context proxy + */ + public Context getDelegateContext() { + return this.delegateContext; + } + + /** + * Recursivley inspect delegates until a non-delegating context is found. + * + * @return The innermost (real) Context that is being delegated to. + */ + public Context getInnermostDelegateContext() { + final Context delegateContext = this.getDelegateContext(); + + if (delegateContext instanceof DelegatingContext) { + return ((DelegatingContext)delegateContext).getInnermostDelegateContext(); + } + + return delegateContext; + } + + /** + * @throws NamingException If the delegate is null, {@link #close()} has been called. + */ + protected void assertOpen() throws NamingException { + if (this.delegateContext == null) { + throw new NamingException("Context is closed."); + } + } + + + //***** Object methods *****// + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Context)) { + return false; + } + + final Context thisContext = this.getInnermostDelegateContext(); + Context otherContext = (Context)obj; + if (otherContext instanceof DelegatingContext) { + otherContext = ((DelegatingContext)otherContext).getInnermostDelegateContext(); + } + + return thisContext == otherContext || (thisContext != null && thisContext.equals(otherContext)); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final Context context = this.getInnermostDelegateContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final Context context = this.getInnermostDelegateContext(); + return (context != null ? context.toString() : "Context is closed"); + } + + + //***** Context Interface Delegates *****// + + /** + * @see javax.naming.Context#addToEnvironment(java.lang.String, java.lang.Object) + */ + public Object addToEnvironment(String propName, Object propVal) throws NamingException { + throw new UnsupportedOperationException("Cannot call addToEnvironment on a pooled context"); + } + + /** + * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object) + */ + public void bind(Name name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().bind(name, obj); + } + + /** + * @see javax.naming.Context#bind(java.lang.String, java.lang.Object) + */ + public void bind(String name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().bind(name, obj); + } + + /** + * @see javax.naming.Context#close() + */ + public void close() throws NamingException { + final Context context = this.getInnermostDelegateContext(); + if (context == null) { + return; + } + + //Get a local reference so the member can be nulled earlier + this.delegateContext = null; + + //Return the object to the Pool and then null the pool reference + try { + this.keyedObjectPool.returnObject(this.dirContextType, context); + } + catch (Exception e) { + final NamingException namingException = new NamingException("Failed to return delegate Context to pool."); + namingException.setRootCause(e); + throw namingException; + } + finally { + this.keyedObjectPool = null; + } + } + + /** + * @see javax.naming.Context#composeName(javax.naming.Name, javax.naming.Name) + */ + public Name composeName(Name name, Name prefix) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().composeName(name, prefix); + } + + /** + * @see javax.naming.Context#composeName(java.lang.String, java.lang.String) + */ + public String composeName(String name, String prefix) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().composeName(name, prefix); + } + + /** + * @see javax.naming.Context#createSubcontext(javax.naming.Name) + */ + public Context createSubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see javax.naming.Context#createSubcontext(java.lang.String) + */ + public Context createSubcontext(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see javax.naming.Context#destroySubcontext(javax.naming.Name) + */ + public void destroySubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see javax.naming.Context#destroySubcontext(java.lang.String) + */ + public void destroySubcontext(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see javax.naming.Context#getEnvironment() + */ + public Hashtable getEnvironment() throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getEnvironment(); + } + + /** + * @see javax.naming.Context#getNameInNamespace() + */ + public String getNameInNamespace() throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameInNamespace(); + } + + /** + * @see javax.naming.Context#getNameParser(javax.naming.Name) + */ + public NameParser getNameParser(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameParser(name); + } + + /** + * @see javax.naming.Context#getNameParser(java.lang.String) + */ + public NameParser getNameParser(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameParser(name); + } + + /** + * @see javax.naming.Context#list(javax.naming.Name) + */ + public NamingEnumeration list(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().list(name); + } + + /** + * @see javax.naming.Context#list(java.lang.String) + */ + public NamingEnumeration list(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().list(name); + } + + /** + * @see javax.naming.Context#listBindings(javax.naming.Name) + */ + public NamingEnumeration listBindings(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().listBindings(name); + } + + /** + * @see javax.naming.Context#listBindings(java.lang.String) + */ + public NamingEnumeration listBindings(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().listBindings(name); + } + + /** + * @see javax.naming.Context#lookup(javax.naming.Name) + */ + public Object lookup(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookup(name); + } + + /** + * @see javax.naming.Context#lookup(java.lang.String) + */ + public Object lookup(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookup(name); + } + + /** + * @see javax.naming.Context#lookupLink(javax.naming.Name) + */ + public Object lookupLink(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookupLink(name); + } + + /** + * @see javax.naming.Context#lookupLink(java.lang.String) + */ + public Object lookupLink(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookupLink(name); + } + + /** + * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object) + */ + public void rebind(Name name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rebind(name, obj); + } + + /** + * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object) + */ + public void rebind(String name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rebind(name, obj); + } + + /** + * @see javax.naming.Context#removeFromEnvironment(java.lang.String) + */ + public Object removeFromEnvironment(String propName) throws NamingException { + throw new UnsupportedOperationException("Cannot call removeFromEnvironment on a pooled context"); + } + + /** + * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name) + */ + public void rename(Name oldName, Name newName) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rename(oldName, newName); + } + + /** + * @see javax.naming.Context#rename(java.lang.String, java.lang.String) + */ + public void rename(String oldName, String newName) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rename(oldName, newName); + } + + /** + * @see javax.naming.Context#unbind(javax.naming.Name) + */ + public void unbind(Name name) throws NamingException { + this.assertOpen(); + this.getDelegateContext().unbind(name); + } + + /** + * @see javax.naming.Context#unbind(java.lang.String) + */ + public void unbind(String name) throws NamingException { + this.assertOpen(); + this.getDelegateContext().unbind(name); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingDirContext.java b/mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingDirContext.java new file mode 100644 index 00000000..defa0d00 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingDirContext.java @@ -0,0 +1,361 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; + +import org.apache.commons.lang.Validate; +import org.apache.commons.pool.KeyedObjectPool; +import org.springframework.ldap.core.DirContextProxy; +import org.springframework.ldap.pool.factory.PoolingContextSource; + + +/** + * Used by {@link PoolingContextSource} to wrap a {@link DirContext}, delegating most methods + * to the underlying context. This class extends {@link DelegatingContext} which handles returning + * the context to the pool on a call to {@link #close()} + * + * @author Eric Dalquist + */ +public class DelegatingDirContext extends DelegatingContext implements DirContext, DirContextProxy { + private DirContext delegateDirContext; + + /** + * Create a new delegating dir context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateDirContext The dir context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingDirContext(KeyedObjectPool keyedObjectPool, DirContext delegateDirContext, DirContextType dirContextType) { + super(keyedObjectPool, delegateDirContext, dirContextType); + Validate.notNull(delegateDirContext, "delegateDirContext may not be null"); + + this.delegateDirContext = delegateDirContext; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this dir context proxy + */ + public DirContext getDelegateDirContext() { + return this.delegateDirContext; + } + + public Context getDelegateContext() { + return this.getDelegateDirContext(); + } + + /** + * Recursivley inspect delegates until a non-delegating dir context is found. + * + * @return The innermost (real) DirContext that is being delegated to. + */ + public DirContext getInnermostDelegateDirContext() { + final DirContext delegateDirContext = this.getDelegateDirContext(); + + if (delegateDirContext instanceof DelegatingDirContext) { + return ((DelegatingDirContext)delegateDirContext).getInnermostDelegateDirContext(); + } + + return delegateDirContext; + } + + protected void assertOpen() throws NamingException { + if (this.delegateDirContext == null) { + throw new NamingException("DirContext is closed."); + } + + super.assertOpen(); + } + + + //***** Object methods *****// + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DirContext)) { + return false; + } + + final DirContext thisDirContext = this.getInnermostDelegateDirContext(); + DirContext otherDirContext = (DirContext)obj; + if (otherDirContext instanceof DelegatingDirContext) { + otherDirContext = ((DelegatingDirContext)otherDirContext).getInnermostDelegateDirContext(); + } + + return thisDirContext == otherDirContext || (thisDirContext != null && thisDirContext.equals(otherDirContext)); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final DirContext context = this.getInnermostDelegateDirContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final DirContext context = this.getInnermostDelegateDirContext(); + return (context != null ? context.toString() : "DirContext is closed"); + } + + + //***** DirContextProxy Interface Methods *****// + + /* (non-Javadoc) + * @see org.springframework.ldap.core.DirContextProxy#getTargetContext() + */ + public DirContext getTargetContext() { + return this.getInnermostDelegateDirContext(); + } + + + //***** DirContext Interface Delegates *****// + + /** + * @see javax.naming.directory.DirContext#bind(javax.naming.Name, java.lang.Object, javax.naming.directory.Attributes) + */ + public void bind(Name name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().bind(name, obj, attrs); + } + + /** + * @see javax.naming.directory.DirContext#bind(java.lang.String, java.lang.Object, javax.naming.directory.Attributes) + */ + public void bind(String name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().bind(name, obj, attrs); + } + + /** + * @see javax.naming.directory.DirContext#createSubcontext(javax.naming.Name, javax.naming.directory.Attributes) + */ + public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#createSubcontext(java.lang.String, javax.naming.directory.Attributes) + */ + public DirContext createSubcontext(String name, Attributes attrs) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(javax.naming.Name, java.lang.String[]) + */ + public Attributes getAttributes(Name name, String[] attrIds) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name, attrIds); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(javax.naming.Name) + */ + public Attributes getAttributes(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(java.lang.String, java.lang.String[]) + */ + public Attributes getAttributes(String name, String[] attrIds) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name, attrIds); + } + + /** + * @see javax.naming.directory.DirContext#getAttributes(java.lang.String) + */ + public Attributes getAttributes(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name); + } + + /** + * @see javax.naming.directory.DirContext#getSchema(javax.naming.Name) + */ + public DirContext getSchema(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchema on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#getSchema(java.lang.String) + */ + public DirContext getSchema(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchema on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#getSchemaClassDefinition(javax.naming.Name) + */ + public DirContext getSchemaClassDefinition(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchemaClassDefinition on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#getSchemaClassDefinition(java.lang.String) + */ + public DirContext getSchemaClassDefinition(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchemaClassDefinition on a pooled context"); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(javax.naming.Name, int, javax.naming.directory.Attributes) + */ + public void modifyAttributes(Name name, int mod_op, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, mod_op, attrs); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(javax.naming.Name, javax.naming.directory.ModificationItem[]) + */ + public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, mods); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(java.lang.String, int, javax.naming.directory.Attributes) + */ + public void modifyAttributes(String name, int mod_op, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, mod_op, attrs); + } + + /** + * @see javax.naming.directory.DirContext#modifyAttributes(java.lang.String, javax.naming.directory.ModificationItem[]) + */ + public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, mods); + } + + /** + * @see javax.naming.directory.DirContext#rebind(javax.naming.Name, java.lang.Object, javax.naming.directory.Attributes) + */ + public void rebind(Name name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().rebind(name, obj, attrs); + } + + /** + * @see javax.naming.directory.DirContext#rebind(java.lang.String, java.lang.Object, javax.naming.directory.Attributes) + */ + public void rebind(String name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().rebind(name, obj, attrs); + } + + /** + * @see javax.naming.directory.DirContext#search(javax.naming.Name, javax.naming.directory.Attributes, java.lang.String[]) + */ + public NamingEnumeration search(Name name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes, attributesToReturn); + } + + /** + * @see javax.naming.directory.DirContext#search(javax.naming.Name, javax.naming.directory.Attributes) + */ + public NamingEnumeration search(Name name, Attributes matchingAttributes) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes); + } + + /** + * @see javax.naming.directory.DirContext#search(javax.naming.Name, java.lang.String, java.lang.Object[], javax.naming.directory.SearchControls) + */ + public NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filterExpr, filterArgs, cons); + } + + /** + * @see javax.naming.directory.DirContext#search(javax.naming.Name, java.lang.String, javax.naming.directory.SearchControls) + */ + public NamingEnumeration search(Name name, String filter, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filter, cons); + } + + /** + * @see javax.naming.directory.DirContext#search(java.lang.String, javax.naming.directory.Attributes, java.lang.String[]) + */ + public NamingEnumeration search(String name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes, attributesToReturn); + } + + /** + * @see javax.naming.directory.DirContext#search(java.lang.String, javax.naming.directory.Attributes) + */ + public NamingEnumeration search(String name, Attributes matchingAttributes) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes); + } + + /** + * @see javax.naming.directory.DirContext#search(java.lang.String, java.lang.String, java.lang.Object[], javax.naming.directory.SearchControls) + */ + public NamingEnumeration search(String name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filterExpr, filterArgs, cons); + } + + /** + * @see javax.naming.directory.DirContext#search(java.lang.String, java.lang.String, javax.naming.directory.SearchControls) + */ + public NamingEnumeration search(String name, String filter, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filter, cons); + } + + /** + * @see DelegatingContext#close() + */ + public void close() throws NamingException { + if (this.delegateDirContext == null) { + return; + } + + super.close(); + this.delegateDirContext = null; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingLdapContext.java b/mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingLdapContext.java new file mode 100644 index 00000000..1c64a16a --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/DelegatingLdapContext.java @@ -0,0 +1,199 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.ExtendedRequest; +import javax.naming.ldap.ExtendedResponse; +import javax.naming.ldap.LdapContext; + +import org.apache.commons.lang.Validate; +import org.apache.commons.pool.KeyedObjectPool; +import org.springframework.ldap.pool.factory.PoolingContextSource; + +/** + * Used by {@link PoolingContextSource} to wrap a {@link LdapContext}, delegating most methods + * to the underlying context. This class extends {@link DelegatingDirContext} which handles returning + * the context to the pool on a call to {@link #close()} + * + * @author Eric Dalquist + */ +public class DelegatingLdapContext extends DelegatingDirContext implements LdapContext { + private LdapContext delegateLdapContext; + + /** + * Create a new delegating ldap context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateLdapContext The ldap context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingLdapContext(KeyedObjectPool keyedObjectPool, LdapContext delegateLdapContext, DirContextType dirContextType) { + super(keyedObjectPool, delegateLdapContext, dirContextType); + Validate.notNull(delegateLdapContext, "delegateLdapContext may not be null"); + + this.delegateLdapContext = delegateLdapContext; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this ldap context proxy + */ + public LdapContext getDelegateLdapContext() { + return this.delegateLdapContext; + } + + // cannot return subtype in overriden method unless Java5 + public DirContext getDelegateDirContext() { + return this.getDelegateLdapContext(); + } + + /** + * Recursivley inspect delegates until a non-delegating ldap context is found. + * + * @return The innermost (real) DirContext that is being delegated to. + */ + public LdapContext getInnermostDelegateLdapContext() { + final LdapContext delegateLdapContext = this.getDelegateLdapContext(); + + if (delegateLdapContext instanceof DelegatingLdapContext) { + return ((DelegatingLdapContext)delegateLdapContext).getInnermostDelegateLdapContext(); + } + + return delegateLdapContext; + } + + protected void assertOpen() throws NamingException { + if (this.delegateLdapContext == null) { + throw new NamingException("LdapContext is closed."); + } + + super.assertOpen(); + } + + + //***** Object methods *****// + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LdapContext)) { + return false; + } + + final LdapContext thisLdapContext = this.getInnermostDelegateLdapContext(); + LdapContext otherLdapContext = (LdapContext)obj; + if (otherLdapContext instanceof DelegatingLdapContext) { + otherLdapContext = ((DelegatingLdapContext)otherLdapContext).getInnermostDelegateLdapContext(); + } + + return thisLdapContext == otherLdapContext || (thisLdapContext != null && thisLdapContext.equals(otherLdapContext)); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final LdapContext context = this.getInnermostDelegateLdapContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final LdapContext context = this.getInnermostDelegateLdapContext(); + return (context != null ? context.toString() : "LdapContext is closed"); + } + + + //***** LdapContext Interface Delegates *****// + + /** + * @see javax.naming.ldap.LdapContext#extendedOperation(javax.naming.ldap.ExtendedRequest) + */ + public ExtendedResponse extendedOperation(ExtendedRequest request) throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().extendedOperation(request); + } + + /** + * @see javax.naming.ldap.LdapContext#getConnectControls() + */ + public Control[] getConnectControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getConnectControls(); + } + + /** + * @see javax.naming.ldap.LdapContext#getRequestControls() + */ + public Control[] getRequestControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getRequestControls(); + } + + /** + * @see javax.naming.ldap.LdapContext#getResponseControls() + */ + public Control[] getResponseControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getResponseControls(); + } + + /** + * @see javax.naming.ldap.LdapContext#newInstance(javax.naming.ldap.Control[]) + */ + public LdapContext newInstance(Control[] requestControls) throws NamingException { + throw new UnsupportedOperationException("Cannot call newInstance on a pooled context"); + } + + /** + * @see javax.naming.ldap.LdapContext#reconnect(javax.naming.ldap.Control[]) + */ + public void reconnect(Control[] connCtls) throws NamingException { + throw new UnsupportedOperationException("Cannot call reconnect on a pooled context"); + } + + /** + * @see javax.naming.ldap.LdapContext#setRequestControls(javax.naming.ldap.Control[]) + */ + public void setRequestControls(Control[] requestControls) throws NamingException { + throw new UnsupportedOperationException("Cannot call setRequestControls on a pooled context"); + } + + /** + * @see DelegatingDirContext#close() + */ + public void close() throws NamingException { + if (this.delegateLdapContext == null) { + return; + } + + super.close(); + this.delegateLdapContext = null; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/DirContextType.java b/mvn-build/core/src/main/java/org/springframework/ldap/pool/DirContextType.java new file mode 100644 index 00000000..93b5ae50 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/DirContextType.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool; + +import javax.naming.directory.DirContext; + +import org.springframework.ldap.core.ContextSource; + + +/** + * An enum representing the two types of {@link DirContext}s that can be returned by a + * {@link ContextSource}. + * + * @author Eric Dalquist + */ +public final class DirContextType { + private String name; + + private DirContextType(String name) { + this.name = name; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + return name; + } + + /** + * The type of {@link DirContext} returned by {@link ContextSource#getReadOnlyContext()} + */ + public static final DirContextType READ_ONLY = new DirContextType("READ_ONLY"); + + /** + * The type of {@link DirContext} returned by {@link ContextSource#getReadWriteContext()} + */ + public static final DirContextType READ_WRITE = new DirContextType("READ_WRITE"); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/DirContextPoolableObjectFactory.java b/mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/DirContextPoolableObjectFactory.java new file mode 100644 index 00000000..b71e82c9 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/DirContextPoolableObjectFactory.java @@ -0,0 +1,204 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool.factory; + +import javax.naming.directory.DirContext; + +import org.apache.commons.lang.Validate; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.pool.BaseKeyedPoolableObjectFactory; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.pool.DirContextType; +import org.springframework.ldap.pool.validation.DirContextValidator; + +/** + * Factory that creates {@link DirContext} instances for pooling via a + * configured {@link ContextSource}. The {@link DirContext}s are keyed based + * on if they are read only or read/write. The expected key type is the + * {@link DirContextType} enum. + * + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyDescriptionRequiredDefault
contextSource The {@link ContextSource} to get {@link DirContext}s from + * for adding to the pool. Yesnull
dirContextValidator The {@link DirContextValidator} to use to validate + * {@link DirContext}s. This is only required if the pool has validation of any + * kind turned on. Nonull
+ * + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + */ +class DirContextPoolableObjectFactory extends BaseKeyedPoolableObjectFactory { + /** + * Logger for this class and subclasses + */ + protected final Log logger = LogFactory.getLog(this.getClass()); + + private ContextSource contextSource; + + private DirContextValidator dirContextValidator; + + /** + * @return the contextSource + */ + public ContextSource getContextSource() { + return this.contextSource; + } + + /** + * @param contextSource + * the contextSource to set + */ + public void setContextSource(ContextSource contextSource) { + if (contextSource == null) { + throw new IllegalArgumentException("contextSource may not be null"); + } + + this.contextSource = contextSource; + } + + /** + * @return the dirContextValidator + */ + public DirContextValidator getDirContextValidator() { + return this.dirContextValidator; + } + + /** + * @param dirContextValidator + * the dirContextValidator to set + */ + public void setDirContextValidator(DirContextValidator dirContextValidator) { + if (dirContextValidator == null) { + throw new IllegalArgumentException( + "dirContextValidator may not be null"); + } + + this.dirContextValidator = dirContextValidator; + } + + /** + * @see org.apache.commons.pool.BaseKeyedPoolableObjectFactory#makeObject(java.lang.Object) + */ + public Object makeObject(Object key) throws Exception { + Validate.notNull(this.contextSource, "ContextSource may not be null"); + Validate.isTrue(key instanceof DirContextType, + "key must be a DirContextType"); + + final DirContextType contextType = (DirContextType) key; + if (this.logger.isDebugEnabled()) { + this.logger.debug("Creating a new " + contextType + " DirContext"); + } + + if (contextType == DirContextType.READ_WRITE) { + final DirContext readWriteContext = this.contextSource + .getReadWriteContext(); + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Created new " + DirContextType.READ_WRITE + + " DirContext='" + readWriteContext + "'"); + } + + return readWriteContext; + } else if (contextType == DirContextType.READ_ONLY) { + + final DirContext readOnlyContext = this.contextSource + .getReadOnlyContext(); + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Created new " + DirContextType.READ_ONLY + + " DirContext='" + readOnlyContext + "'"); + } + + return readOnlyContext; + } else { + throw new IllegalArgumentException("Unrecognized ContextType: " + + contextType); + } + } + + /** + * @see org.apache.commons.pool.BaseKeyedPoolableObjectFactory#validateObject(java.lang.Object, + * java.lang.Object) + */ + public boolean validateObject(Object key, Object obj) { + Validate.notNull(this.dirContextValidator, + "DirContextValidator may not be null"); + Validate.isTrue(key instanceof DirContextType, + "key must be a DirContextType"); + Validate.isTrue(obj instanceof DirContext, + "The Object to validate must be of type '" + DirContext.class + + "'"); + + try { + final DirContextType contextType = (DirContextType) key; + final DirContext dirContext = (DirContext) obj; + return this.dirContextValidator.validateDirContext(contextType, + dirContext); + } catch (Exception e) { + this.logger.warn("Failed to validate '" + obj + + "' due to an unexpected exception.", e); + return false; + } + } + + + /** + * @see org.apache.commons.pool.BaseKeyedPoolableObjectFactory#destroyObject(java.lang.Object, + * java.lang.Object) + */ + public void destroyObject(Object key, Object obj) throws Exception { + Validate.isTrue(obj instanceof DirContext, + "The Object to validate must be of type '" + DirContext.class + + "'"); + + try { + final DirContext dirContext = (DirContext) obj; + if (this.logger.isDebugEnabled()) { + this.logger.debug("Closing " + key + " DirContext='" + + dirContext + "'"); + } + dirContext.close(); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Closed " + key + " DirContext='" + + dirContext + "'"); + } + } catch (Exception e) { + this.logger.warn( + "An exception occured while closing '" + obj + "'", e); + } + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/PoolingContextSource.java b/mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/PoolingContextSource.java new file mode 100644 index 00000000..1e5b1f87 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/PoolingContextSource.java @@ -0,0 +1,406 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool.factory; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.pool.impl.GenericKeyedObjectPool; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.ldap.NamingException; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.pool.DelegatingDirContext; +import org.springframework.ldap.pool.DelegatingLdapContext; +import org.springframework.ldap.pool.DirContextType; +import org.springframework.ldap.pool.validation.DirContextValidator; + +/** + * A {@link ContextSource} implementation that wraps an object pool and another {@link ContextSource}. + * {@link DirContext}s are retrieved from the pool which maintains them. + * + * + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyDescriptionRequiredDefault
contextSource + * The {@link ContextSource} to get {@link DirContext}s from for adding to the pool. + * Yesnull
dirContextValidator + * The {@link DirContextValidator} to use for validating {@link DirContext}s. Required + * if any of the test/validate options are enabled. + * Nonull
minIdle{@link GenericKeyedObjectPool#setMinIdle(int)}No0
maxIdle{@link GenericKeyedObjectPool#setMaxIdle(int)}No8
maxActive{@link GenericKeyedObjectPool#setMaxActive(int)}No8
maxTotal{@link GenericKeyedObjectPool#setMaxTotal(int)}No-1
maxWait{@link GenericKeyedObjectPool#setMaxWait(long)}No-1L
whenExhaustedAction{@link GenericKeyedObjectPool#setWhenExhaustedAction(byte)}No{@link GenericKeyedObjectPool#WHEN_EXHAUSTED_BLOCK}
testOnBorrow{@link GenericKeyedObjectPool#setTestOnBorrow(boolean)}Nofalse
testOnReturn{@link GenericKeyedObjectPool#setTestOnReturn(boolean)}Nofalse
testWhileIdle{@link GenericKeyedObjectPool#setTestWhileIdle(boolean)}Nofalse
timeBetweenEvictionRunsMillis{@link GenericKeyedObjectPool#setTimeBetweenEvictionRunsMillis(long)}No-1L
minEvictableIdleTimeMillis{@link GenericKeyedObjectPool#setMinEvictableIdleTimeMillis(long)}No1000L * 60L * 30L
numTestsPerEvictionRun{@link GenericKeyedObjectPool#setNumTestsPerEvictionRun(int)}No3
+ * + * + * @author Eric Dalquist + */ +public class PoolingContextSource implements ContextSource, DisposableBean { + /** + * The logger for this class and sub-classes + */ + protected final Log logger = LogFactory.getLog(this.getClass()); + + private final GenericKeyedObjectPool keyedObjectPool; + private final DirContextPoolableObjectFactory dirContextPoolableObjectFactory; + + /** + * Creates a new pooling context source, setting up the DirContext object factory + * and generic keyed object pool. + */ + public PoolingContextSource() { + this.dirContextPoolableObjectFactory = new DirContextPoolableObjectFactory(); + this.keyedObjectPool = new GenericKeyedObjectPool(); + this.keyedObjectPool.setFactory(this.dirContextPoolableObjectFactory); + } + + + //***** Pool Property Configuration *****// + + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getMaxActive() + */ + public int getMaxActive() { + return this.keyedObjectPool.getMaxActive(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getMaxIdle() + */ + public int getMaxIdle() { + return this.keyedObjectPool.getMaxIdle(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getMaxTotal() + */ + public int getMaxTotal() { + return this.keyedObjectPool.getMaxTotal(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getMaxWait() + */ + public long getMaxWait() { + return this.keyedObjectPool.getMaxWait(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getMinEvictableIdleTimeMillis() + */ + public long getMinEvictableIdleTimeMillis() { + return this.keyedObjectPool.getMinEvictableIdleTimeMillis(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getMinIdle() + */ + public int getMinIdle() { + return this.keyedObjectPool.getMinIdle(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getNumActive() + */ + public int getNumActive() { + return this.keyedObjectPool.getNumActive(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getNumIdle() + */ + public int getNumIdle() { + return this.keyedObjectPool.getNumIdle(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getNumTestsPerEvictionRun() + */ + public int getNumTestsPerEvictionRun() { + return this.keyedObjectPool.getNumTestsPerEvictionRun(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getTestOnBorrow() + */ + public boolean getTestOnBorrow() { + return this.keyedObjectPool.getTestOnBorrow(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getTestOnReturn() + */ + public boolean getTestOnReturn() { + return this.keyedObjectPool.getTestOnReturn(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getTestWhileIdle() + */ + public boolean getTestWhileIdle() { + return this.keyedObjectPool.getTestWhileIdle(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getTimeBetweenEvictionRunsMillis() + */ + public long getTimeBetweenEvictionRunsMillis() { + return this.keyedObjectPool.getTimeBetweenEvictionRunsMillis(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#getWhenExhaustedAction() + */ + public byte getWhenExhaustedAction() { + return this.keyedObjectPool.getWhenExhaustedAction(); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setMaxActive(int) + */ + public void setMaxActive(int maxActive) { + this.keyedObjectPool.setMaxActive(maxActive); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setMaxIdle(int) + */ + public void setMaxIdle(int maxIdle) { + this.keyedObjectPool.setMaxIdle(maxIdle); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setMaxTotal(int) + */ + public void setMaxTotal(int maxTotal) { + this.keyedObjectPool.setMaxTotal(maxTotal); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setMaxWait(long) + */ + public void setMaxWait(long maxWait) { + this.keyedObjectPool.setMaxWait(maxWait); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setMinEvictableIdleTimeMillis(long) + */ + public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { + this.keyedObjectPool.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setMinIdle(int) + */ + public void setMinIdle(int poolSize) { + this.keyedObjectPool.setMinIdle(poolSize); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setNumTestsPerEvictionRun(int) + */ + public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) { + this.keyedObjectPool.setNumTestsPerEvictionRun(numTestsPerEvictionRun); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setTestOnBorrow(boolean) + */ + public void setTestOnBorrow(boolean testOnBorrow) { + this.keyedObjectPool.setTestOnBorrow(testOnBorrow); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setTestOnReturn(boolean) + */ + public void setTestOnReturn(boolean testOnReturn) { + this.keyedObjectPool.setTestOnReturn(testOnReturn); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setTestWhileIdle(boolean) + */ + public void setTestWhileIdle(boolean testWhileIdle) { + this.keyedObjectPool.setTestWhileIdle(testWhileIdle); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setTimeBetweenEvictionRunsMillis(long) + */ + public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { + this.keyedObjectPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + } + /** + * @see org.apache.commons.pool.impl.GenericKeyedObjectPool#setWhenExhaustedAction(byte) + */ + public void setWhenExhaustedAction(byte whenExhaustedAction) { + this.keyedObjectPool.setWhenExhaustedAction(whenExhaustedAction); + } + + + //***** Object Factory Property Configuration *****// + + /** + * @return the contextSource + */ + public ContextSource getContextSource() { + return this.dirContextPoolableObjectFactory.getContextSource(); + } + /** + * @return the dirContextValidator + */ + public DirContextValidator getDirContextValidator() { + return this.dirContextPoolableObjectFactory.getDirContextValidator(); + } + /** + * @param contextSource the contextSource to set + * @Required + */ + public void setContextSource(ContextSource contextSource) { + this.dirContextPoolableObjectFactory.setContextSource(contextSource); + } + /** + * @param dirContextValidator the dirContextValidator to set + * @Required + */ + public void setDirContextValidator(DirContextValidator dirContextValidator) { + this.dirContextPoolableObjectFactory.setDirContextValidator(dirContextValidator); + } + + + //***** DisposableBean interface methods *****// + + /* (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + try { + this.keyedObjectPool.close(); + } + catch (Exception e) { + this.logger.warn("An exception occured while closing the underlying pool.", e); + } + } + + + //***** ContextSource interface methods *****// + + /* + * @see ContextSource#getReadOnlyContext() + */ + public DirContext getReadOnlyContext() throws NamingException { + return this.getContext(DirContextType.READ_ONLY); + } + + /* + * @see ContextSource#getReadWriteContext() + */ + public DirContext getReadWriteContext() throws NamingException { + return this.getContext(DirContextType.READ_WRITE); + } + + /** + * Gets a DirContext of the specified type from the keyed object pool. + * + * @param dirContextType The type of context to return. + * @return A wrapped DirContext of the specified type. + * @throws DataAccessResourceFailureException If retreiving the object from the pool throws an exception + */ + protected DirContext getContext(DirContextType dirContextType) { + final DirContext dirContext; + try { + dirContext = (DirContext)this.keyedObjectPool.borrowObject(dirContextType); + } + catch (Exception e) { + throw new DataAccessResourceFailureException("Failed to borrow DirContext from pool.", e); + } + + if (dirContext instanceof LdapContext) { + return new DelegatingLdapContext(this.keyedObjectPool, (LdapContext)dirContext, dirContextType); + } + + return new DelegatingDirContext(this.keyedObjectPool, dirContext, dirContextType); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/package.html new file mode 100644 index 00000000..bcf1cd83 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/factory/package.html @@ -0,0 +1,7 @@ + + + +Core classes for the pooling library. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/pool/package.html new file mode 100644 index 00000000..85bee723 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/package.html @@ -0,0 +1,7 @@ + + + +Base classes for the pooling library. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/DefaultDirContextValidator.java b/mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/DefaultDirContextValidator.java new file mode 100644 index 00000000..450e88e4 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/DefaultDirContextValidator.java @@ -0,0 +1,173 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool.validation; + +import javax.naming.NamingEnumeration; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; + +import org.apache.commons.lang.Validate; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.pool.DirContextType; + +/** + * Default {@link DirContext} validator that executes {@link DirContext#search(String, String, SearchControls)}. The + * name, filter and {@link SearchControls} are all configurable. There is no special handling for read only versus + * read write {@link DirContext}s. + * + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyDescriptionRequiredDefault
base + * The name parameter to the search method. + * No""
filter + * The filter parameter to the search method. + * No"objectclass=*"
searchControls + * The {@link SearchControls} parameter to the search method. + * No + * {@link SearchControls#setCountLimit(long)} = 1
+ * {@link SearchControls#setReturningAttributes(String[])} = new String[] { "objectclass" }
+ * {@link SearchControls#setTimeLimit(int)} = 500 + *
+ * + * @author Eric Dalquist + */ +public class DefaultDirContextValidator implements DirContextValidator { + /** + * Logger for this class and sub-classes + */ + protected final Log logger = LogFactory.getLog(this.getClass()); + + private String base; + private String filter; + private SearchControls searchControls; + + /** + * Create the default validator, creates {@link SearchControls} with a countLimit of 1, returningAttributes of + * objectclass and timeLimit of 500. The default base is an empty string and the default filter is objectclass=* + */ + public DefaultDirContextValidator() { + this.searchControls = new SearchControls(); + this.searchControls.setCountLimit(1); + this.searchControls.setReturningAttributes(new String[] { "objectclass" }); + this.searchControls.setTimeLimit(500); + + this.base = ""; + + this.filter = "objectclass=*"; + } + + /** + * @return the baseName + */ + public String getBase() { + return this.base; + } + /** + * @param base the baseName to set + */ + public void setBase(String base) { + this.base = base; + } + /** + * @return the filter + */ + public String getFilter() { + return this.filter; + } + /** + * @param filter the filter to set + */ + public void setFilter(String filter) { + if (filter == null) { + throw new IllegalArgumentException("filter may not be null"); + } + + this.filter = filter; + } + /** + * @return the searchControls + */ + public SearchControls getSearchControls() { + return this.searchControls; + } + /** + * @param searchControls the searchControls to set + */ + public void setSearchControls(SearchControls searchControls) { + if (searchControls == null) { + throw new IllegalArgumentException("searchControls may not be null"); + } + + this.searchControls = searchControls; + } + + + /** + * @see DirContextValidator#validateDirContext(DirContextType, javax.naming.directory.DirContext) + */ + public boolean validateDirContext(DirContextType contextType, DirContext dirContext) { + Validate.notNull(contextType, "contextType may not be null"); + Validate.notNull(dirContext, "dirContext may not be null"); + + try { + final NamingEnumeration searchResults = dirContext.search(this.base, this.filter, this.searchControls); + + if (searchResults.hasMore()) { + if (this.logger.isDebugEnabled()) { + this.logger.debug("DirContext '" + dirContext + "' passed validation."); + } + + return true; + } + } + catch (Exception e) { + this.logger.warn("DirContext '" + dirContext + "' failed validation with an exception.", e); + } + + if (this.logger.isInfoEnabled()) { + this.logger.info("DirContext '" + dirContext + "' failed validation."); + } + return false; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/DirContextValidator.java b/mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/DirContextValidator.java new file mode 100644 index 00000000..5eab0faf --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/DirContextValidator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool.validation; + +import javax.naming.directory.DirContext; + +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.pool.DirContextType; + +/** + * A validator for {@link DirContext}s. + * + * @author Eric Dalquist + */ +public interface DirContextValidator { + /** + * Validates the {@link DirContext}. A valid {@link DirContext} should be able + * to answer queries and if applicable write to the directory. + * + * @param contextType The type of the {@link DirContext}, refers to if {@link ContextSource#getReadOnlyContext()} or {@link ContextSource#getReadWriteContext()} was called to create the {@link DirContext} + * @param dirContext The {@link DirContext} to validate. + * @return true if the {@link DirContext} operated correctly during validation. + */ + public boolean validateDirContext(DirContextType contextType, DirContext dirContext); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/package.html new file mode 100644 index 00000000..788f99a5 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/pool/validation/package.html @@ -0,0 +1,7 @@ + + + +Connection validation support for the pooling library. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/support/LdapUtils.java b/mvn-build/core/src/main/java/org/springframework/ldap/support/LdapUtils.java new file mode 100644 index 00000000..ba03e977 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/support/LdapUtils.java @@ -0,0 +1,209 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.support; + +import javax.naming.directory.DirContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.NamingException; +import org.springframework.util.Assert; + +/** + * Generic utility methods for working with LDAP. Mainly for internal use within + * the framework, but also useful for custom code. + * + * @author Ulrik Sandberg + * @since 1.2 + */ +public final class LdapUtils { + + private static final Log logger = LogFactory.getLog(LdapUtils.class); + + /** + * Not to be instantiated. + */ + private LdapUtils() { + + } + + /** + * Close the given JNDI Context and ignore any thrown exception. This is + * useful for typical finally blocks in JNDI code. + * + * @param context the JNDI Context to close (may be null) + */ + public static void closeContext(DirContext context) { + if (context != null) { + try { + context.close(); + } + catch (NamingException ex) { + logger.debug("Could not close JNDI DirContext", ex); + } + catch (Throwable ex) { + // We don't trust the JNDI provider: It might throw + // RuntimeException or Error. + logger.debug("Unexpected exception on closing JNDI DirContext", ex); + } + } + } + + /** + * Convert the specified checked + * {@link javax.naming.NamingException NamingException} to a Spring LDAP + * runtime {@link org.springframework.ldap.NamingException NamingException} + * equivalent. + * + * @param ex the original checked NamingException to convert + * @return the Spring LDAP runtime NamingException wrapping the given + * exception + */ + public static NamingException convertLdapException(javax.naming.NamingException ex) { + Assert.notNull(ex, "NamingException must not be null"); + + if (ex.getClass().equals(javax.naming.directory.AttributeInUseException.class)) { + return new org.springframework.ldap.AttributeInUseException( + (javax.naming.directory.AttributeInUseException) ex); + } + if (ex.getClass().equals(javax.naming.directory.AttributeModificationException.class)) { + return new org.springframework.ldap.AttributeModificationException( + (javax.naming.directory.AttributeModificationException) ex); + } + if (ex.getClass().equals(javax.naming.CannotProceedException.class)) { + return new org.springframework.ldap.CannotProceedException((javax.naming.CannotProceedException) ex); + } + if (ex.getClass().equals(javax.naming.CommunicationException.class)) { + return new org.springframework.ldap.CommunicationException((javax.naming.CommunicationException) ex); + } + if (ex.getClass().equals(javax.naming.ConfigurationException.class)) { + return new org.springframework.ldap.ConfigurationException((javax.naming.ConfigurationException) ex); + } + if (ex.getClass().equals(javax.naming.ContextNotEmptyException.class)) { + return new org.springframework.ldap.ContextNotEmptyException((javax.naming.ContextNotEmptyException) ex); + } + if (ex.getClass().equals(javax.naming.InsufficientResourcesException.class)) { + return new org.springframework.ldap.InsufficientResourcesException( + (javax.naming.InsufficientResourcesException) ex); + } + if (ex.getClass().equals(javax.naming.InterruptedNamingException.class)) { + return new org.springframework.ldap.InterruptedNamingException((javax.naming.InterruptedNamingException) ex); + } + if (ex.getClass().equals(javax.naming.directory.InvalidAttributeIdentifierException.class)) { + return new org.springframework.ldap.InvalidAttributeIdentifierException( + (javax.naming.directory.InvalidAttributeIdentifierException) ex); + } + if (ex.getClass().equals(javax.naming.directory.InvalidAttributesException.class)) { + return new org.springframework.ldap.InvalidAttributesException( + (javax.naming.directory.InvalidAttributesException) ex); + } + if (ex.getClass().equals(javax.naming.directory.InvalidAttributeValueException.class)) { + return new org.springframework.ldap.InvalidAttributeValueException( + (javax.naming.directory.InvalidAttributeValueException) ex); + } + if (ex.getClass().equals(javax.naming.InvalidNameException.class)) { + return new org.springframework.ldap.InvalidNameException((javax.naming.InvalidNameException) ex); + } + if (ex.getClass().equals(javax.naming.directory.InvalidSearchControlsException.class)) { + return new org.springframework.ldap.InvalidSearchControlsException( + (javax.naming.directory.InvalidSearchControlsException) ex); + } + if (ex.getClass().equals(javax.naming.directory.InvalidSearchFilterException.class)) { + return new org.springframework.ldap.InvalidSearchFilterException( + (javax.naming.directory.InvalidSearchFilterException) ex); + } + + // this class is abstract, so it can never be of exactly this class; using instanceof + if (ex instanceof javax.naming.ldap.LdapReferralException) { + return new org.springframework.ldap.LdapReferralException((javax.naming.ldap.LdapReferralException) ex); + } + // Skipping the abstract superclass javax.naming.ReferralException + + // LimitExceededException hierarchy + if (ex.getClass().equals(javax.naming.SizeLimitExceededException.class)) { + return new org.springframework.ldap.SizeLimitExceededException((javax.naming.SizeLimitExceededException) ex); + } + if (ex.getClass().equals(javax.naming.TimeLimitExceededException.class)) { + return new org.springframework.ldap.TimeLimitExceededException((javax.naming.TimeLimitExceededException) ex); + } + // this class is the superclass of the two above + if (ex.getClass().equals(javax.naming.LimitExceededException.class)) { + return new org.springframework.ldap.LimitExceededException((javax.naming.LimitExceededException) ex); + } + + // LinkException hierarchy + if (ex.getClass().equals(javax.naming.LinkLoopException.class)) { + return new org.springframework.ldap.LinkLoopException((javax.naming.LinkLoopException) ex); + } + if (ex.getClass().equals(javax.naming.MalformedLinkException.class)) { + return new org.springframework.ldap.MalformedLinkException((javax.naming.MalformedLinkException) ex); + } + // this class is the superclass of the two above + if (ex.getClass().equals(javax.naming.LinkException.class)) { + return new org.springframework.ldap.LinkException((javax.naming.LinkException) ex); + } + + if (ex.getClass().equals(javax.naming.NameAlreadyBoundException.class)) { + return new org.springframework.ldap.NameAlreadyBoundException((javax.naming.NameAlreadyBoundException) ex); + } + if (ex.getClass().equals(javax.naming.NameNotFoundException.class)) { + return new org.springframework.ldap.NameNotFoundException((javax.naming.NameNotFoundException) ex); + } + + // NamingSecurityException hierarchy + if (ex.getClass().equals(javax.naming.NoPermissionException.class)) { + return new org.springframework.ldap.NoPermissionException((javax.naming.NoPermissionException) ex); + } + if (ex.getClass().equals(javax.naming.AuthenticationException.class)) { + return new org.springframework.ldap.AuthenticationException((javax.naming.AuthenticationException) ex); + } + if (ex.getClass().equals(javax.naming.AuthenticationNotSupportedException.class)) { + return new org.springframework.ldap.AuthenticationNotSupportedException( + (javax.naming.AuthenticationNotSupportedException) ex); + } + // Skipping the abstract superclass javax.naming.NamingSecurityException + + if (ex.getClass().equals(javax.naming.NoInitialContextException.class)) { + return new org.springframework.ldap.NoInitialContextException((javax.naming.NoInitialContextException) ex); + } + if (ex.getClass().equals(javax.naming.directory.NoSuchAttributeException.class)) { + return new org.springframework.ldap.NoSuchAttributeException( + (javax.naming.directory.NoSuchAttributeException) ex); + } + if (ex.getClass().equals(javax.naming.NotContextException.class)) { + return new org.springframework.ldap.NotContextException((javax.naming.NotContextException) ex); + } + if (ex.getClass().equals(javax.naming.OperationNotSupportedException.class)) { + return new org.springframework.ldap.OperationNotSupportedException( + (javax.naming.OperationNotSupportedException) ex); + } + if (ex.getClass().equals(javax.naming.PartialResultException.class)) { + return new org.springframework.ldap.PartialResultException((javax.naming.PartialResultException) ex); + } + if (ex.getClass().equals(javax.naming.directory.SchemaViolationException.class)) { + return new org.springframework.ldap.SchemaViolationException( + (javax.naming.directory.SchemaViolationException) ex); + } + if (ex.getClass().equals(javax.naming.ServiceUnavailableException.class)) { + return new org.springframework.ldap.ServiceUnavailableException( + (javax.naming.ServiceUnavailableException) ex); + } + + // fallback + return new org.springframework.ldap.UncategorizedLdapException(ex); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/support/ListComparator.java b/mvn-build/core/src/main/java/org/springframework/ldap/support/ListComparator.java new file mode 100644 index 00000000..1e5c9807 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/support/ListComparator.java @@ -0,0 +1,65 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.support; + +import java.util.Comparator; +import java.util.List; + +/** + * Comparator for comparing lists of Comparable objects. + * + * @author Mattias Arthursson + */ +public class ListComparator implements Comparator { + + /** + * Compare two lists of Comparable objects. + * + * @param o1 the first object to be compared. + * @param o2 the second object to be compared. + * @throws ClassCastException if any of the lists contains an object that + * is not Comparable. + */ + public int compare(Object o1, Object o2) { + List list1 = (List) o1; + List list2 = (List) o2; + + for (int i = 0; i < list1.size(); i++) { + if (list2.size() > i) { + Comparable component1 = (Comparable) list1.get(i); + Comparable component2 = (Comparable) list2.get(i); + int componentsCompared = component1.compareTo(component2); + if (componentsCompared != 0) { + return componentsCompared; + } + } + else { + // First instance has more components, so that instance is + // greater. + return 1; + } + } + + // All components so far are equal - if the other instance has + // more components it is greater otherwise they are equal. + if (list2.size() > list1.size()) { + return -1; + } + else { + return 0; + } + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/support/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/support/package.html new file mode 100644 index 00000000..5eabecc8 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/support/package.html @@ -0,0 +1,7 @@ + + + +Support classes for Spring-LDAP. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/BindOperationExecutor.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/BindOperationExecutor.java new file mode 100644 index 00000000..27805ae0 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/BindOperationExecutor.java @@ -0,0 +1,127 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; +import javax.naming.directory.Attributes; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +/** + * A {@link CompensatingTransactionOperationExecutor} to manage a bind + * operation. Performs a bind in {@link #performOperation()}, a corresponding + * unbind in {@link #rollback()}, and nothing in {@link #commit()}. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class BindOperationExecutor implements + CompensatingTransactionOperationExecutor { + private static Log log = LogFactory.getLog(BindOperationExecutor.class); + + private LdapOperations ldapOperations; + + private Name dn; + + private Object originalObject; + + private Attributes originalAttributes; + + /** + * Constructor. + * + * @param ldapOperations + * {@link LdapOperations} to use for performing the rollback + * operation. + * @param dn + * DN of the entry to be unbound. + * @param originalObject + * original value sent to the 'object' parameter of the bind + * operation. + * @param originalAttributes + * original value sent to the 'attributes' parameter of the bind + * operation. + */ + public BindOperationExecutor(LdapOperations ldapOperations, Name dn, + Object originalObject, Attributes originalAttributes) { + this.ldapOperations = ldapOperations; + this.dn = dn; + this.originalObject = originalObject; + this.originalAttributes = originalAttributes; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + try { + ldapOperations.unbind(dn); + } catch (Exception e) { + log.warn("Failed to rollback, dn:" + dn.toString(), e); + } + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#commit() + */ + public void commit() { + log.debug("Nothing to do in commit for bind operation"); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#performOperation() + */ + public void performOperation() { + log.debug("Performing bind operation"); + ldapOperations.bind(dn, originalObject, originalAttributes); + } + + /** + * Get the DN. Package private for testing purposes. + * + * @return the target DN. + */ + Name getDn() { + return dn; + } + + /** + * Get the LdapOperations. Package private for testing purposes. + * + * @return the LdapOperations. + */ + LdapOperations getLdapOperations() { + return ldapOperations; + } + + Attributes getOriginalAttributes() { + return originalAttributes; + } + + Object getOriginalObject() { + return originalObject; + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/BindOperationRecorder.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/BindOperationRecorder.java new file mode 100644 index 00000000..d6f8d179 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/BindOperationRecorder.java @@ -0,0 +1,79 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +/** + * A {@link CompensatingTransactionOperationRecorder} keeping track of bind + * operations. Creates {@link BindOperationExecutor} objects in + * {@link #recordOperation(Object[])}. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class BindOperationRecorder implements + CompensatingTransactionOperationRecorder { + + private LdapOperations ldapOperations; + + /** + * Constructor. + * + * @param ldapOperations + * {@link LdapOperations} to use for supplying to the + * corresponding rollback operation. + */ + public BindOperationRecorder(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + if (args == null || args.length != 3) { + throw new IllegalArgumentException( + "Invalid arguments for bind operation"); + } + Name dn = LdapTransactionUtils.getFirstArgumentAsName(args); + Object object = args[1]; + Attributes attributes = null; + if (args[2] != null && !(args[2] instanceof Attributes)) { + throw new IllegalArgumentException( + "Invalid third argument to bind operation"); + } else if (args[2] != null) { + attributes = (Attributes) args[2]; + } + + return new BindOperationExecutor(ldapOperations, dn, object, attributes); + } + + /** + * Get the LdapOperations. For testing purposes.s + * + * @return the LdapOperations. + */ + LdapOperations getLdapOperations() { + return ldapOperations; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactory.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactory.java new file mode 100644 index 00000000..968b7396 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactory.java @@ -0,0 +1,194 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import javax.naming.directory.DirContext; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.NamingException; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.DirContextProxy; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.transaction.compensating.CompensatingTransactionOperationFactory; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +/** + * {@link CompensatingTransactionOperationRecorder} implementation for LDAP + * operations. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class LdapCompensatingTransactionOperationFactory implements + CompensatingTransactionOperationFactory { + private static Log log = LogFactory + .getLog(LdapCompensatingTransactionOperationFactory.class); + + private TempEntryRenamingStrategy renamingStrategy; + + /** + * Constructor. + * + * @param renamingStrategy + * the {@link TempEntryRenamingStrategy} to supply to relevant + * operations. + */ + public LdapCompensatingTransactionOperationFactory( + TempEntryRenamingStrategy renamingStrategy) { + this.renamingStrategy = renamingStrategy; + } + + /* + * @see org.springframework.transaction.compensating.CompensatingTransactionOperationFactory#createRecordingOperation(java.lang.Object, + * java.lang.String) + */ + public CompensatingTransactionOperationRecorder createRecordingOperation( + Object resource, String operation) { + if (StringUtils + .equals(operation, LdapTransactionUtils.BIND_METHOD_NAME)) { + log.debug("Bind operation recorded"); + return new BindOperationRecorder( + createLdapOperationsInstance((DirContext) resource)); + } else if (StringUtils.equals(operation, + LdapTransactionUtils.REBIND_METHOD_NAME)) { + log.debug("Rebind operation recorded"); + return new RebindOperationRecorder( + createLdapOperationsInstance((DirContext) resource), + renamingStrategy); + } else if (StringUtils.equals(operation, + LdapTransactionUtils.RENAME_METHOD_NAME)) { + log.debug("Rename operation recorded"); + return new RenameOperationRecorder( + createLdapOperationsInstance((DirContext) resource)); + } else if (StringUtils.equals(operation, + LdapTransactionUtils.MODIFY_ATTRIBUTES_METHOD_NAME)) { + return new ModifyAttributesOperationRecorder( + createLdapOperationsInstance((DirContext) resource)); + } else if (StringUtils.equals(operation, + LdapTransactionUtils.UNBIND_METHOD_NAME)) { + return new UnbindOperationRecorder( + createLdapOperationsInstance((DirContext) resource), + renamingStrategy); + } + + log + .warn("No suitable CompensatingTransactionOperationRecorder found for method " + + operation + ". Operation will not be transacted."); + return new NullOperationRecorder(); + } + + LdapOperations createLdapOperationsInstance(DirContext ctx) { + return new LdapTemplate(new SingleContextSource(ctx)); + } + + /** + * A {@link ContextSource} implementation using returning + * {@link NonClosingDirContextInvocationHandler} proxies on the same + * DirContext instance for each call. + * + * @author Mattias Arthursson + */ + static class SingleContextSource implements ContextSource { + private DirContext ctx; + + /** + * Constructor. + * + * @param ctx + * the target DirContext. + */ + public SingleContextSource(DirContext ctx) { + this.ctx = ctx; + } + + /* + * @see org.springframework.ldap.ContextSource#getReadOnlyContext() + */ + public DirContext getReadOnlyContext() throws NamingException { + return getNonClosingDirContextProxy(ctx); + } + + /* + * @see org.springframework.ldap.ContextSource#getReadWriteContext() + */ + public DirContext getReadWriteContext() throws NamingException { + return getNonClosingDirContextProxy(ctx); + } + + private DirContext getNonClosingDirContextProxy(DirContext context) { + return (DirContext) Proxy.newProxyInstance(DirContextProxy.class + .getClassLoader(), new Class[] { + LdapTransactionUtils.getActualTargetClass(context), + DirContextProxy.class }, + new NonClosingDirContextInvocationHandler(context)); + + } + } + + /** + * A proxy for DirContext forwarding all operation to the target DirContext, + * but making sure that no close operations will be + * performed. + * + * @author Mattias Arthursson + */ + public static class NonClosingDirContextInvocationHandler implements + InvocationHandler { + + private DirContext target; + + public NonClosingDirContextInvocationHandler(DirContext target) { + this.target = target; + } + + /* + * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, + * java.lang.reflect.Method, java.lang.Object[]) + */ + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + + String methodName = method.getName(); + if (methodName.equals("getTargetContext")) { + return target; + } else if (methodName.equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); + } else if (methodName.equals("hashCode")) { + // Use hashCode of Connection proxy. + return new Integer(proxy.hashCode()); + } else if (methodName.equals("close")) { + // Never close the target context, as this class will only be + // used for operations concerning the compensating transactions. + return null; + } + + try { + return method.invoke(target, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/LdapTransactionUtils.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/LdapTransactionUtils.java new file mode 100644 index 00000000..4f56746c --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/LdapTransactionUtils.java @@ -0,0 +1,121 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import org.apache.commons.lang.StringUtils; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.util.Assert; + +/** + * Utility methods for working with LDAP transactions. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public final class LdapTransactionUtils { + + public static final String REBIND_METHOD_NAME = "rebind"; + + public static final String BIND_METHOD_NAME = "bind"; + + public static final String RENAME_METHOD_NAME = "rename"; + + public static final String UNBIND_METHOD_NAME = "unbind"; + + public static final String MODIFY_ATTRIBUTES_METHOD_NAME = "modifyAttributes"; + + /** + * Not to be instantiated. + */ + private LdapTransactionUtils() { + + } + + /** + * Get the first parameter in the argument list as a Name. + * + * @param args + * arguments supplied to a ldap operation. + * @return a Name representation of the first argument, or the Name itself + * if it is a name. + */ + public static Name getFirstArgumentAsName(Object[] args) { + Assert.notEmpty(args); + + Object firstArg = args[0]; + return getArgumentAsName(firstArg); + } + + /** + * Get the argument as a Name. + * + * @param arg + * an argument supplied to an Ldap operation. + * @return a Name representation of the argument, or the Name itself if it + * is a Name. + */ + public static Name getArgumentAsName(Object arg) { + if (arg instanceof String) { + return new DistinguishedName((String) arg); + } else if (arg instanceof Name) { + return (Name) arg; + } else { + throw new IllegalArgumentException( + "First argument needs to be a Name or a String representation thereof"); + } + } + + /** + * Check whether the supplied method is a method for which transactions is + * supported (and which should be recorded for possible rollback later). + * + * @param methodName + * name of the method to check. + * @return true if this is a supported transaction operation, + * false otherwise. + */ + public static boolean isSupportedWriteTransactionOperation(String methodName) { + return (StringUtils.equals(methodName, BIND_METHOD_NAME) + || StringUtils.equals(methodName, REBIND_METHOD_NAME) + || StringUtils.equals(methodName, RENAME_METHOD_NAME) + || StringUtils + .equals(methodName, MODIFY_ATTRIBUTES_METHOD_NAME) || StringUtils + .equals(methodName, UNBIND_METHOD_NAME)); + + } + + /** + * Get the actual class of the supplied DirContext instance; LdapContext or + * DirContext. + * + * @param context + * the DirContext instance to check. + * @return LdapContext.class if context is an LdapContext, DirContext.class + * otherwise. + */ + public static Class getActualTargetClass(DirContext context) { + if (context instanceof LdapContext) { + return LdapContext.class; + } + + return DirContext.class; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutor.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutor.java new file mode 100644 index 00000000..c413ef68 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutor.java @@ -0,0 +1,116 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; +import javax.naming.directory.ModificationItem; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +/** + * A {@link CompensatingTransactionOperationExecutor} to manage a + * modifyAttributes operation. Performs a + * modifyAttributes in {@link #performOperation()}, a negating + * modifyAttributes in {@link #rollback()}, and nothing in {@link #commit()}. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class ModifyAttributesOperationExecutor implements + CompensatingTransactionOperationExecutor { + + private static Log log = LogFactory + .getLog(ModifyAttributesOperationExecutor.class); + + private LdapOperations ldapOperations; + + private Name dn; + + private ModificationItem[] compensatingModifications; + + private ModificationItem[] actualModifications; + + /** + * Constructor. + * + * @param ldapOperations + * The {@link LdapOperations} to use to perform the rollback + * operation. + * @param dn + * the DN of the target entry. + * @param actualModifications + * the actual modificationItems that were sent to the + * modifyAttributes operation. + * @param compensatingModifications + * the ModificationItems to undo the recorded operation. + */ + public ModifyAttributesOperationExecutor(LdapOperations ldapOperations, + Name dn, ModificationItem[] actualModifications, + ModificationItem[] compensatingModifications) { + this.ldapOperations = ldapOperations; + this.dn = dn; + this.actualModifications = actualModifications; + this.compensatingModifications = compensatingModifications; + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + try { + log.debug("Rolling back modifyAttributes operation"); + ldapOperations.modifyAttributes(dn, compensatingModifications); + } catch (Exception e) { + log + .warn("Failed to rollback ModifyAttributes operation, dn: " + + dn); + } + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#commit() + */ + public void commit() { + log.debug("Nothing to do in commit for modifyAttributes"); + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#performOperation() + */ + public void performOperation() { + log.debug("Performing modifyAttributes operation"); + ldapOperations.modifyAttributes(dn, actualModifications); + } + + Name getDn() { + return dn; + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } + + ModificationItem[] getActualModifications() { + return actualModifications; + } + + ModificationItem[] getCompensatingModifications() { + return compensatingModifications; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorder.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorder.java new file mode 100644 index 00000000..0a96ccef --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorder.java @@ -0,0 +1,165 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import java.util.HashSet; +import java.util.Set; + +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; + +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; +import org.springframework.util.Assert; + +/** + * A {@link CompensatingTransactionOperationRecorder} keeping track of + * modifyAttributes operations, creating corresponding + * {@link ModifyAttributesOperationExecutor} instances for rollback. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class ModifyAttributesOperationRecorder implements + CompensatingTransactionOperationRecorder { + + private LdapOperations ldapOperations; + + public ModifyAttributesOperationRecorder(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + Assert.notNull(args); + Name dn = LdapTransactionUtils.getFirstArgumentAsName(args); + if (args.length != 2 || !(args[1] instanceof ModificationItem[])) { + throw new IllegalArgumentException( + "Unexpected arguments to ModifyAttributes operation"); + } + + ModificationItem[] incomingModifications = (ModificationItem[]) args[1]; + + Set set = new HashSet(); + for (int i = 0; i < incomingModifications.length; i++) { + set.add(incomingModifications[i].getAttribute().getID()); + } + + // Get the current values of all referred Attributes. + String[] attributeNameArray = (String[]) set.toArray(new String[set + .size()]); + Attributes currentAttributes = (Attributes) ldapOperations.lookup(dn, + attributeNameArray, getAttributesMapper()); + + // Get a compensating ModificationItem for each of the incoming + // modification. + ModificationItem[] rollbackItems = new ModificationItem[incomingModifications.length]; + for (int i = 0; i < incomingModifications.length; i++) { + rollbackItems[i] = getCompensatingModificationItem( + currentAttributes, incomingModifications[i]); + } + + return new ModifyAttributesOperationExecutor(ldapOperations, dn, + incomingModifications, rollbackItems); + } + + /** + * Get an {@link AttributesMapper} that just returns the supplied + * Attributes. + * + * @return the {@link AttributesMapper} to use for getting the current + * Attributes of the target DN. + */ + AttributesMapper getAttributesMapper() { + return new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + return attributes; + } + }; + } + + /** + * Get a ModificationItem to use for rollback of the supplied modification. + * + * @param originalAttributes + * All Attributes of the target DN that are affected of any of + * the ModificationItems. + * @param modificationItem + * the ModificationItem to create a rollback item for. + * @return A ModificationItem to use for rollback of the supplied + * ModificationItem. + */ + protected ModificationItem getCompensatingModificationItem( + Attributes originalAttributes, ModificationItem modificationItem) { + Attribute modificationAttribute = modificationItem.getAttribute(); + Attribute originalAttribute = originalAttributes + .get(modificationAttribute.getID()); + + if (modificationItem.getModificationOp() == DirContext.REMOVE_ATTRIBUTE) { + if (modificationAttribute.size() == 0) { + // If the modification attribute size it means that the + // Attribute should be removed entirely - we should store a + // ModificationItem to restore all present values for rollback. + return new ModificationItem(DirContext.ADD_ATTRIBUTE, + (Attribute) originalAttribute.clone()); + } else { + // The rollback modification will be to re-add the removed + // attribute values. + return new ModificationItem(DirContext.ADD_ATTRIBUTE, + (Attribute) modificationAttribute.clone()); + } + } else if (modificationItem.getModificationOp() == DirContext.REPLACE_ATTRIBUTE) { + if (originalAttribute != null) { + return new ModificationItem(DirContext.REPLACE_ATTRIBUTE, + (Attribute) originalAttribute.clone()); + } else { + // The attribute doesn't previously exist - the rollback + // operation will be to remove the attribute. + return new ModificationItem(DirContext.REMOVE_ATTRIBUTE, + new BasicAttribute(modificationAttribute.getID())); + } + } else { + // An ADD_ATTRIBUTE operation + if (originalAttribute == null) { + // The attribute doesn't previously exist - the rollback + // operation will be to remove the attribute. + return new ModificationItem(DirContext.REMOVE_ATTRIBUTE, + new BasicAttribute(modificationAttribute.getID())); + } else { + // The attribute does exist before - we should store the + // previous value and it should be used for replacing in + // rollback. + return new ModificationItem(DirContext.REPLACE_ATTRIBUTE, + (Attribute) originalAttribute.clone()); + } + } + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/NullOperationExecutor.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/NullOperationExecutor.java new file mode 100644 index 00000000..9600c4a8 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/NullOperationExecutor.java @@ -0,0 +1,47 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +/** + * A {@link CompensatingTransactionOperationExecutor} that performs nothing. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class NullOperationExecutor implements + CompensatingTransactionOperationExecutor { + + private static Log log = LogFactory.getLog(NullOperationExecutor.class); + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + log.info("Rolling back null operation"); + } + + public void commit() { + log.info("Committing back null operation"); + } + + public void performOperation() { + log.info("Performing null operation"); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/NullOperationRecorder.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/NullOperationRecorder.java new file mode 100644 index 00000000..4c5cebd3 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/NullOperationRecorder.java @@ -0,0 +1,43 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import org.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +/** + * A {@link CompensatingTransactionOperationRecorder} performing nothing, + * returning a {@link NullOperationExecutor} regardless of the input. Instances + * of this class will be created if the + * {@link CompensatingTransactionOperationManager} cannot determine any + * appropriate {@link CompensatingTransactionOperationRecorder} for the current + * operation. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class NullOperationRecorder implements + CompensatingTransactionOperationRecorder { + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + return new NullOperationExecutor(); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RebindOperationExecutor.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RebindOperationExecutor.java new file mode 100644 index 00000000..18b4a3d6 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RebindOperationExecutor.java @@ -0,0 +1,135 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; +import javax.naming.directory.Attributes; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +/** + * A {@link CompensatingTransactionOperationExecutor} to manage a rebind + * operation. The methods in this class do not behave as expected, since it + * might be impossible to retrieve all the original attributes from the entry. + * Instead this class performs a rename in {@link #performOperation()}, + * a negating rename in {@link #rollback()}, and the {@link #commit()} + * operation unbinds the original entry from its temporary location and binds a + * new entry to the original location using the attributes supplied to the + * original rebind opertaion. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class RebindOperationExecutor implements + CompensatingTransactionOperationExecutor { + + private static Log log = LogFactory.getLog(RebindOperationExecutor.class); + + private LdapOperations ldapOperations; + + private Name originalDn; + + private Name temporaryDn; + + private Object originalObject; + + private Attributes originalAttributes; + + /** + * Constructor. + * + * @param ldapOperations + * the {@link LdapOperations} to use to perform the rollback. + * @param originalDn + * The original DN of the entry to bind. + * @param temporaryDn + * The temporary DN of the entry. + * @param originalObject + * Original 'object' parameter sent to the rebind operation. + * @param originalAttributes + * Original 'attributes' parameter sent to the rebind operation + */ + public RebindOperationExecutor(LdapOperations ldapOperations, + Name originalDn, Name temporaryDn, Object originalObject, + Attributes originalAttributes) { + this.ldapOperations = ldapOperations; + this.originalDn = originalDn; + this.temporaryDn = temporaryDn; + this.originalObject = originalObject; + this.originalAttributes = originalAttributes; + } + + /** + * Get the LdapOperations. Package private for testing purposes. + * + * @return the LdapOperations. + */ + LdapOperations getLdapOperations() { + return ldapOperations; + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + log.debug("Rolling back rebind operation"); + try { + ldapOperations.unbind(originalDn); + ldapOperations.rename(temporaryDn, originalDn); + } catch (Exception e) { + log.warn("Failed to rollback operation, dn: " + originalDn + + "; temporary DN: " + temporaryDn, e); + } + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#commit() + */ + public void commit() { + log.debug("Committing rebind operation"); + ldapOperations.unbind(temporaryDn); + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#performOperation() + */ + public void performOperation() { + log.debug("Performing rebind operation - " + + "renaming original entry and " + + "binding new contents to entry."); + ldapOperations.rename(originalDn, temporaryDn); + ldapOperations.bind(originalDn, originalObject, originalAttributes); + } + + Attributes getOriginalAttributes() { + return originalAttributes; + } + + Name getOriginalDn() { + return originalDn; + } + + Object getOriginalObject() { + return originalObject; + } + + Name getTemporaryDn() { + return temporaryDn; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RebindOperationRecorder.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RebindOperationRecorder.java new file mode 100644 index 00000000..2d832b8c --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RebindOperationRecorder.java @@ -0,0 +1,93 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +/** + * A {@link CompensatingTransactionOperationRecorder} keeping track of a rebind + * operation. Creates {@link RebindOperationExecutor} objects in + * {@link #recordOperation(Object[])}. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class RebindOperationRecorder implements + CompensatingTransactionOperationRecorder { + + private LdapOperations ldapOperations; + + private TempEntryRenamingStrategy renamingStrategy; + + /** + * Constructor. + * + * @param ldapOperations + * {@link LdapOperations} to use for getting the rollback + * information and supply to the {@link RebindOperationExecutor}. + * @param renamingStrategy + * {@link TempEntryRenamingStrategy} to use for generating temp + * DNs. + */ + public RebindOperationRecorder(LdapOperations ldapOperations, + TempEntryRenamingStrategy renamingStrategy) { + this.ldapOperations = ldapOperations; + this.renamingStrategy = renamingStrategy; + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + if (args == null || args.length != 3) { + throw new IllegalArgumentException( + "Invalid arguments for bind operation"); + } + Name dn = LdapTransactionUtils.getFirstArgumentAsName(args); + Object object = args[1]; + Attributes attributes = null; + if (args[2] != null && !(args[2] instanceof Attributes)) { + throw new IllegalArgumentException( + "Invalid third argument to bind operation"); + } else if (args[2] != null) { + attributes = (Attributes) args[2]; + } + + Name temporaryName = renamingStrategy.getTemporaryName(dn); + + return new RebindOperationExecutor(ldapOperations, dn, temporaryName, + object, attributes); + } + + /** + * Get the LdapOperations. For testing purposes. + * + * @return the LdapOperations. + */ + LdapOperations getLdapOperations() { + return ldapOperations; + } + + public TempEntryRenamingStrategy getRenamingStrategy() { + return renamingStrategy; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RenameOperationExecutor.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RenameOperationExecutor.java new file mode 100644 index 00000000..a8d59ae3 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RenameOperationExecutor.java @@ -0,0 +1,101 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +/** + * A {@link CompensatingTransactionOperationExecutor} to manage a rename + * operation. Performs a rename operation in {@link #performOperation()}, a + * negating rename in {@link #rollback()}, and nothing in {@link #commit()}. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class RenameOperationExecutor implements + CompensatingTransactionOperationExecutor { + + private static Log log = LogFactory.getLog(RenameOperationExecutor.class); + + private LdapOperations ldapOperations; + + private Name newDn; + + private Name originalDn; + + /** + * Constructor. + * + * @param ldapOperations + * The {@link LdapOperations} to use for performing the rollback + * operation. + * @param originalDn + * DN that the entry was moved from in the recorded operation. + * @param newDn + * DN that the entry has been moved to in the recorded operation. + */ + public RenameOperationExecutor(LdapOperations ldapOperations, + Name originalDn, Name newDn) { + this.ldapOperations = ldapOperations; + this.originalDn = originalDn; + this.newDn = newDn; + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + log.debug("Rolling back rename operation"); + try { + ldapOperations.rename(newDn, originalDn); + } catch (Exception e) { + log.warn("Unable to rollback rename operation. " + "originalDn: " + + newDn + "; newDn: " + originalDn); + } + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#commit() + */ + public void commit() { + log.debug("Nothing to do in commit for rename operation"); + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#performOperation() + */ + public void performOperation() { + log.debug("Performing rename operation"); + ldapOperations.rename(originalDn, newDn); + } + + Name getNewDn() { + return newDn; + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } + + Name getOriginalDn() { + return originalDn; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RenameOperationRecorder.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RenameOperationRecorder.java new file mode 100644 index 00000000..e9bb8952 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/RenameOperationRecorder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; +import org.springframework.util.Assert; + +/** + * A {@link CompensatingTransactionOperationRecorder} for keeping track of + * rename operations. Creates {@link RenameOperationExecutor} objects for + * rolling back. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class RenameOperationRecorder implements + CompensatingTransactionOperationRecorder { + + private static Log log = LogFactory.getLog(RenameOperationRecorder.class); + + private LdapOperations ldapOperations; + + /** + * Constructor. + * + * @param ldapOperations + * The {@link LdapOperations} to supply to the created + * {@link RebindOperationExecutor} objects. + */ + public RenameOperationRecorder(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + log.debug("Storing rollback information for rename operation"); + Assert.notEmpty(args); + if (args.length != 2) { + // This really shouldn't happen. + throw new IllegalArgumentException("Illegal argument length"); + } + Name oldDn = LdapTransactionUtils.getArgumentAsName(args[0]); + Name newDn = LdapTransactionUtils.getArgumentAsName(args[1]); + return new RenameOperationExecutor(ldapOperations, oldDn, newDn); + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/TempEntryRenamingStrategy.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/TempEntryRenamingStrategy.java new file mode 100644 index 00000000..1a47f7ab --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/TempEntryRenamingStrategy.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; + +/** + * Interface for different strategies to rename temporary entries for unbind and + * rebind operations. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public interface TempEntryRenamingStrategy { + + /** + * Get a temporary name for the current entry to be renamed to. + * + * @param originalName + * The original name of the entry. + * @return The name to which the entry should be temporarily renamed + * according to this strategy. + */ + public Name getTemporaryName(Name originalName); +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/UnbindOperationExecutor.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/UnbindOperationExecutor.java new file mode 100644 index 00000000..2e75ec70 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/UnbindOperationExecutor.java @@ -0,0 +1,106 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +/** + * A {@link CompensatingTransactionOperationExecutor} to manage an unbind + * operation. The methods in this class do not behave as expected, since it + * might be impossible to retrieve all the original attributes from the entry. + * Instead this class performs a rename in {@link #performOperation()}, + * a negating rename in {@link #rollback()}, and {@link #commit()} unbinds the + * entry from its temporary location. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class UnbindOperationExecutor implements + CompensatingTransactionOperationExecutor { + + private static Log log = LogFactory.getLog(UnbindOperationExecutor.class); + + private LdapOperations ldapOperations; + + private Name originalDn; + + private Name temporaryDn; + + /** + * Constructor. + * + * @param ldapOperations + * The {@link LdapOperations} to use for performing the rollback + * operation. + * @param originalDn + * The original DN of the entry to be removed. + * @param temporaryDn + * Temporary DN of the entry to be removed; this is where the + * entry is temporarily stored during the transaction. + */ + public UnbindOperationExecutor(LdapOperations ldapOperations, + Name originalDn, Name temporaryDn) { + this.ldapOperations = ldapOperations; + this.originalDn = originalDn; + this.temporaryDn = temporaryDn; + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#rollback() + */ + public void rollback() { + try { + ldapOperations.rename(temporaryDn, originalDn); + } catch (Exception e) { + log.warn("Filed to rollback unbind operation, temporaryDn: " + + temporaryDn + "; originalDn: " + originalDn); + } + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#commit() + */ + public void commit() { + log.debug("Committing unbind operation - unbinding temporary entry"); + ldapOperations.unbind(temporaryDn); + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationExecutor#performOperation() + */ + public void performOperation() { + log.debug("Performing operation for unbind -" + + " renaming to temporary entry."); + ldapOperations.rename(originalDn, temporaryDn); + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } + + Name getOriginalDn() { + return originalDn; + } + + Name getTemporaryDn() { + return temporaryDn; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/UnbindOperationRecorder.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/UnbindOperationRecorder.java new file mode 100644 index 00000000..d34e40ed --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/UnbindOperationRecorder.java @@ -0,0 +1,74 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; + +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +/** + * {@link CompensatingTransactionOperationRecorder} to keep track of unbind + * operations. This class creates {@link UnbindOperationExecutor} objects for + * rollback. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class UnbindOperationRecorder implements + CompensatingTransactionOperationRecorder { + + private LdapOperations ldapOperations; + + private TempEntryRenamingStrategy renamingStrategy; + + /** + * Constructor. + * + * @param ldapOperations + * {@link LdapOperations} to use for getting the data prior to + * unbinding the entry and to supply to the + * {@link UnbindOperationExecutor} for rollback. + * @param renamingStrategy + * the {@link TempEntryRenamingStrategy} to use when generating + * DNs for temporary entries. + */ + public UnbindOperationRecorder(LdapOperations ldapOperations, + TempEntryRenamingStrategy renamingStrategy) { + this.ldapOperations = ldapOperations; + this.renamingStrategy = renamingStrategy; + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationRecorder#recordOperation(java.lang.Object[]) + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args) { + Name dn = LdapTransactionUtils.getFirstArgumentAsName(args); + Name temporaryDn = renamingStrategy.getTemporaryName(dn); + + return new UnbindOperationExecutor(ldapOperations, dn, temporaryDn); + } + + LdapOperations getLdapOperations() { + return ldapOperations; + } + + public TempEntryRenamingStrategy getRenamingStrategy() { + return renamingStrategy; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManager.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManager.java new file mode 100644 index 00000000..bdd31683 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManager.java @@ -0,0 +1,202 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionSuspensionNotSupportedException; +import org.springframework.transaction.support.DefaultTransactionStatus; + +/** + * A Transaction Manager to manage LDAP and JDBC operations within the same + * transaction. Note that even though the same logical transaction is used, this + * is not a JTA XA transaction; no two-phase commit will be performed, + * and thus commit and rollback may yield unexpected results. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class ContextSourceAndDataSourceTransactionManager extends + DataSourceTransactionManager { + + private static final long serialVersionUID = 6832868697460384648L; + + private ContextSourceTransactionManagerDelegate ldapManagerDelegate = new ContextSourceTransactionManagerDelegate(); + + public ContextSourceAndDataSourceTransactionManager() { + super(); + // Override the default behaviour. + setNestedTransactionAllowed(false); + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#isExistingTransaction(java.lang.Object) + */ + protected boolean isExistingTransaction(Object transaction) { + ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) transaction; + + return super.isExistingTransaction(actualTransactionObject + .getDataSourceTransactionObject()); + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction() + */ + protected Object doGetTransaction() throws TransactionException { + Object dataSourceTransactionObject = super.doGetTransaction(); + Object contextSourceTransactionObject = ldapManagerDelegate + .doGetTransaction(); + + return new ContextSourceAndDataSourceTransactionObject( + contextSourceTransactionObject, dataSourceTransactionObject); + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin(java.lang.Object, + * org.springframework.transaction.TransactionDefinition) + */ + protected void doBegin(Object transaction, TransactionDefinition definition) + throws TransactionException { + ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) transaction; + + super.doBegin(actualTransactionObject.getDataSourceTransactionObject(), + definition); + ldapManagerDelegate.doBegin(actualTransactionObject + .getLdapTransactionObject(), definition); + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doCleanupAfterCompletion(java.lang.Object) + */ + protected void doCleanupAfterCompletion(Object transaction) { + ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) transaction; + + super.doCleanupAfterCompletion(actualTransactionObject + .getDataSourceTransactionObject()); + ldapManagerDelegate.doCleanupAfterCompletion(actualTransactionObject + .getLdapTransactionObject()); + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doCommit(org.springframework.transaction.support.DefaultTransactionStatus) + */ + protected void doCommit(DefaultTransactionStatus status) + throws TransactionException { + + ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) status + .getTransaction(); + + try { + super.doCommit(new DefaultTransactionStatus(actualTransactionObject + .getDataSourceTransactionObject(), status + .isNewTransaction(), status.isNewSynchronization(), status + .isReadOnly(), status.isDebug(), status + .getSuspendedResources())); + } catch (TransactionException ex) { + if (isRollbackOnCommitFailure()) { + logger.debug("Failed to commit db resource, rethrowing", ex); + // If we are to rollback on commit failure, just rethrow the + // exception - this will cause a rollback to be performed on + // both resources. + throw ex; + } else { + logger + .warn("Failed to commit and resource is rollbackOnCommit not set -" + + " proceeding to commit ldap resource."); + } + } + ldapManagerDelegate.doCommit(new DefaultTransactionStatus( + actualTransactionObject.getLdapTransactionObject(), status + .isNewTransaction(), status.isNewSynchronization(), + status.isReadOnly(), status.isDebug(), status + .getSuspendedResources())); + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus) + */ + protected void doRollback(DefaultTransactionStatus status) + throws TransactionException { + ContextSourceAndDataSourceTransactionObject actualTransactionObject = (ContextSourceAndDataSourceTransactionObject) status + .getTransaction(); + + super.doRollback(new DefaultTransactionStatus(actualTransactionObject + .getDataSourceTransactionObject(), status.isNewTransaction(), + status.isNewSynchronization(), status.isReadOnly(), status + .isDebug(), status.getSuspendedResources())); + ldapManagerDelegate.doRollback(new DefaultTransactionStatus( + actualTransactionObject.getLdapTransactionObject(), status + .isNewTransaction(), status.isNewSynchronization(), + status.isReadOnly(), status.isDebug(), status + .getSuspendedResources())); + } + + public ContextSource getContextSource() { + return ldapManagerDelegate.getContextSource(); + } + + public void setContextSource(ContextSource contextSource) { + ldapManagerDelegate.setContextSource(contextSource); + } + + protected void setRenamingStrategy( + TempEntryRenamingStrategy renamingStrategy) { + ldapManagerDelegate.setRenamingStrategy(renamingStrategy); + } + + private class ContextSourceAndDataSourceTransactionObject { + private Object ldapTransactionObject; + + private Object dataSourceTransactionObject; + + public ContextSourceAndDataSourceTransactionObject( + Object ldapTransactionObject, Object dataSourceTransactionObject) { + this.ldapTransactionObject = ldapTransactionObject; + this.dataSourceTransactionObject = dataSourceTransactionObject; + } + + public Object getDataSourceTransactionObject() { + return dataSourceTransactionObject; + } + + public Object getLdapTransactionObject() { + return ldapTransactionObject; + } + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doSuspend(java.lang.Object) + */ + protected Object doSuspend(Object transaction) throws TransactionException { + throw new TransactionSuspensionNotSupportedException( + "Transaction manager [" + getClass().getName() + + "] does not support transaction suspension"); + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doResume(java.lang.Object, + * java.lang.Object) + */ + protected void doResume(Object transaction, Object suspendedResources) + throws TransactionException { + throw new TransactionSuspensionNotSupportedException( + "Transaction manager [" + getClass().getName() + + "] does not support transaction suspension"); + } +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManager.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManager.java new file mode 100644 index 00000000..a35e0144 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManager.java @@ -0,0 +1,190 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; +import org.springframework.ldap.transaction.compensating.UnbindOperationExecutor; +import org.springframework.ldap.transaction.compensating.support.DefaultTempEntryRenamingStrategy; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; +import org.springframework.transaction.compensating.support.DefaultCompensatingTransactionOperationManager; +import org.springframework.transaction.support.AbstractPlatformTransactionManager; +import org.springframework.transaction.support.DefaultTransactionStatus; + +/** + * TransactionManager for managing LDAP transactions. Since transactions are not + * supported in the LDAP protocol, this class and its collaborators aim to + * provide compensating transactions instead. Should a transaction + * need to be rolled back, this TransactionManager will try to restore the + * original state using information recorded prior to each operation. The + * operation where the original state is restored is called a compensating + * operation. + *

+ * NOTE: The transactions provided by this TransactionManager are all + * client side and are by no means 'real' transactions, in the sense + * that we know them in the ordinary database world, e.g.: + *

    + *
  • Should the transaction failure be caused by a network failure, there is + * no way whatsoever that this TransactionManager can restore the database + * state. In this case, all possibilities for rollback will be utterly lost.
  • + *
  • Transaction isolation is not provided, i.e. entries participating in a + * transaction for one client may very well participate in another transaction + * for another client at the same time. Should one of these transactions be + * rolled back, the outcome of this is undetermined, and may in the worst case + * result in total failure.
  • + *
+ *

+ *

+ * While the points above should be noted and considered, the compensating + * transaction approach will be perfectly sufficient for all but the most + * unfortunate of circumstances. Considering that there currently is a total + * absence of server-side transaction support in the LDAP world, being able to + * mark operations as transactional in the same way as for relational database + * operations is surely a step forward. + *

+ *

+ * An LDAP transaction is tied to a {@link ContextSource}, to be supplied to + * the {@link #setContextSource(ContextSource)} method. While the actual + * ContextSource used by the target LdapTemplate instance needs to be of the + * type {@link TransactionAwareContextSourceProxy}, the ContextSource supplied + * to this class should be the actual target ContextSource. + *

+ *

+ * Using this TransactionManager along with + * {@link TransactionAwareContextSourceProxy}, all modifying operations (bind, + * unbind, rebind, rename, modifyAttributes) in a transaction will be + * intercepted. Each modification has its corresponding + * {@link CompensatingTransactionOperationRecorder}, which collects the + * information necessary to perform a rollback and produces a + * {@link CompensatingTransactionOperationExecutor} which is then used to + * execute the actual operation and is later called for performing the commit or + * rollback. + *

+ *

+ * For several of the operations, performing a rollback is pretty + * straightforward. For example, in order to roll back a rename operation, it + * will only be required to rename the entry back to its original position. For + * other operations, however, it's a bit more complicated. An unbind operation + * is not possible to roll back by simply binding the entry back with the + * attributes retrieved from the original entry. It might not be possible to get + * all the information from the original entry. Consequently, the + * {@link UnbindOperationExecutor} will move the original entry to a temporary + * location in its performOperation() method. The commit() method will know that + * everything went well, so it will be OK to unbind the entry. The rollback + * operation will be to rename the entry back to its original location. The same + * behaviour is used for rebind() operations. The operation of calculating a + * temporary location for an entry is delegated to a + * {@link TempEntryRenamingStrategy} (default + * {@link DefaultTempEntryRenamingStrategy}), specified in + * {@link #setRenamingStrategy(TempEntryRenamingStrategy)}. + *

+ *

+ * The actual work of this Transaction Manager is delegated to a + * {@link ContextSourceTransactionManagerDelegate}. This is because the exact + * same logic needs to be used if we want to wrap a JDBC and LDAP transaction in + * the same logical transaction. + *

+ * + * @author Mattias Arthursson + * + * @see ContextSourceAndDataSourceTransactionManager + * @see ContextSourceTransactionManagerDelegate + * @see DefaultCompensatingTransactionOperationManager + * @see TempEntryRenamingStrategy + * @see TransactionAwareContextSourceProxy + * @since 1.2 + */ +public class ContextSourceTransactionManager extends + AbstractPlatformTransactionManager { + + private static final long serialVersionUID = 7138208218687237856L; + + private ContextSourceTransactionManagerDelegate delegate = new ContextSourceTransactionManagerDelegate(); + + /* + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doBegin(java.lang.Object, + * org.springframework.transaction.TransactionDefinition) + */ + protected void doBegin(Object transaction, TransactionDefinition definition) + throws TransactionException { + delegate.doBegin(transaction, definition); + } + + /* + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doCleanupAfterCompletion(java.lang.Object) + */ + protected void doCleanupAfterCompletion(Object transaction) { + delegate.doCleanupAfterCompletion(transaction); + } + + /* + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doCommit(org.springframework.transaction.support.DefaultTransactionStatus) + */ + protected void doCommit(DefaultTransactionStatus status) + throws TransactionException { + delegate.doCommit(status); + } + + /* + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doGetTransaction() + */ + protected Object doGetTransaction() throws TransactionException { + return delegate.doGetTransaction(); + } + + /* + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus) + */ + protected void doRollback(DefaultTransactionStatus status) + throws TransactionException { + delegate.doRollback(status); + } + + /** + * Get the ContextSource. + * + * @return the contextSource. + * @see ContextSourceTransactionManagerDelegate#getContextSource() + */ + public ContextSource getContextSource() { + return delegate.getContextSource(); + } + + /** + * Set the ContextSource. + * + * @param contextSource + * the ContextSource. + * @see ContextSourceTransactionManagerDelegate#setContextSource(ContextSource) + */ + public void setContextSource(ContextSource contextSource) { + delegate.setContextSource(contextSource); + } + + /** + * Set the {@link TempEntryRenamingStrategy}. + * + * @param renamingStrategy + * the Renaming Strategy. + * @see ContextSourceTransactionManagerDelegate#setRenamingStrategy(TempEntryRenamingStrategy) + */ + public void setRenamingStrategy(TempEntryRenamingStrategy renamingStrategy) { + delegate.setRenamingStrategy(renamingStrategy); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java new file mode 100644 index 00000000..77f699d9 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerDelegate.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.transaction.compensating.LdapCompensatingTransactionOperationFactory; +import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; +import org.springframework.ldap.transaction.compensating.support.DefaultTempEntryRenamingStrategy; +import org.springframework.transaction.compensating.support.AbstractCompensatingTransactionManagerDelegate; +import org.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport; +import org.springframework.transaction.compensating.support.DefaultCompensatingTransactionOperationManager; + +/** + * This delegate performs all the work for the + * {@link ContextSourceTransactionManager}. The work is delegated in order to + * be able to perform the exact same work for the LDAP part in + * {@link ContextSourceAndDataSourceTransactionManager}. + * + * @author Mattias Arthursson + * @see ContextSourceTransactionManager + * @see ContextSourceAndDataSourceTransactionManager + * @since 1.2 + */ +public class ContextSourceTransactionManagerDelegate extends + AbstractCompensatingTransactionManagerDelegate { + + private static final Log log = LogFactory + .getLog(ContextSourceTransactionManagerDelegate.class); + + private ContextSource contextSource; + + private TempEntryRenamingStrategy renamingStrategy = new DefaultTempEntryRenamingStrategy(); + + /** + * Set the ContextSource to work on. Even though the actual ContextSource + * sent to the LdapTemplate instance should be a + * {@link TransactionAwareContextSourceProxy}, the one sent to this method + * should be the target of that proxy. If it is not, the target will be + * extracted and used instead. + * + * @param contextSource + * the ContextSource to work on. + */ + public void setContextSource(ContextSource contextSource) { + if (contextSource instanceof TransactionAwareContextSourceProxy) { + TransactionAwareContextSourceProxy proxy = (TransactionAwareContextSourceProxy) contextSource; + this.contextSource = proxy.getTarget(); + } else { + this.contextSource = contextSource; + } + } + + public ContextSource getContextSource() { + return contextSource; + } + + /* + * @see org.springframework.transaction.compensating.support.AbstractCompensatingTransactionManagerDelegate#getTransactionSynchronizationKey() + */ + protected Object getTransactionSynchronizationKey() { + return getContextSource(); + } + + /* + * @see org.springframework.transaction.compensating.support.AbstractCompensatingTransactionManagerDelegate#getNewHolder() + */ + protected CompensatingTransactionHolderSupport getNewHolder() { + DirContext newCtx = getContextSource().getReadOnlyContext(); + DirContextHolder contextHolder = new DirContextHolder( + new DefaultCompensatingTransactionOperationManager( + new LdapCompensatingTransactionOperationFactory( + renamingStrategy)), newCtx); + return contextHolder; + } + + /* + * @see org.springframework.transaction.compensating.support.AbstractCompensatingTransactionManagerDelegate#closeTargetResource(org.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport) + */ + protected void closeTargetResource( + CompensatingTransactionHolderSupport transactionHolderSupport) { + DirContextHolder contextHolder = (DirContextHolder) transactionHolderSupport; + DirContext ctx = contextHolder.getCtx(); + + try { + log.debug("Closing target context"); + ctx.close(); + } catch (NamingException e) { + log.warn("Failed to close target context", e); + } + } + + /** + * Set the {@link TempEntryRenamingStrategy} to be used when renaming + * temporary entries in unbind and rebind operations. Default value is a + * {@link DefaultTempEntryRenamingStrategy}. + * + * @param renamingStrategy + * the {@link TempEntryRenamingStrategy} to use. + */ + public void setRenamingStrategy(TempEntryRenamingStrategy renamingStrategy) { + this.renamingStrategy = renamingStrategy; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/DirContextHolder.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/DirContextHolder.java new file mode 100644 index 00000000..7d0c1ce6 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/DirContextHolder.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import javax.naming.directory.DirContext; + +import org.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import org.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport; + +/** + * Keeps track of the transaction DirContext. The same DirContext instance will + * be reused throughout a transaction. Also keeps a + * {@link CompensatingTransactionOperationManager}, responsible for performing + * operations and keeping track of all changes and storing information necessary + * for commit or rollback. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class DirContextHolder extends CompensatingTransactionHolderSupport { + private DirContext ctx; + + /** + * Constructor. + * + * @param manager + * The {@link CompensatingTransactionOperationManager}. + * @param ctx + * The DirContext associated with the current transaction. + */ + public DirContextHolder(CompensatingTransactionOperationManager manager, + DirContext ctx) { + super(manager); + this.ctx = ctx; + } + + /** + * Set the DirContext associated with the current transaction. + * + * @param ctx + * The DirContext associated with the current transaction. + */ + public void setCtx(DirContext ctx) { + this.ctx = ctx; + } + + /** + * Return the DirContext associated with the current transaction. + */ + public DirContext getCtx() { + return ctx; + } + + /* + * @see org.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport#getTransactedResource() + */ + protected Object getTransactedResource() { + return ctx; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxy.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxy.java new file mode 100644 index 00000000..cacf52d8 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxy.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import java.lang.reflect.Proxy; + +import javax.naming.directory.DirContext; + +import org.springframework.ldap.NamingException; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.DirContextProxy; +import org.springframework.ldap.transaction.compensating.LdapTransactionUtils; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * A proxy for ContextSource to make sure that the returned DirContext objects + * are aware of the surrounding transactions. This makes sure that the + * DirContext is not closed during the transaction and that all modifying + * operations are recorded, keeping track of the corresponding rollback + * operations. All returned DirContext instances will be of the type + * {@link TransactionAwareDirContextInvocationHandler}. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class TransactionAwareContextSourceProxy implements ContextSource { + private ContextSource target; + + /** + * Constructor. + * + * @param target + * the target ContextSource. + */ + public TransactionAwareContextSourceProxy(ContextSource target) { + this.target = target; + } + + /** + * Get the target ContextSource. + * + * @return the target ContextSource. + */ + public ContextSource getTarget() { + return target; + } + + /* + * @see org.springframework.ldap.core.ContextSource#getReadOnlyContext() + */ + public DirContext getReadOnlyContext() throws NamingException { + return getReadWriteContext(); + } + + private DirContext getTransactionAwareDirContextProxy(DirContext context, + ContextSource target) { + return (DirContext) Proxy + .newProxyInstance(DirContextProxy.class.getClassLoader(), + new Class[] { + LdapTransactionUtils + .getActualTargetClass(context), + DirContextProxy.class }, + new TransactionAwareDirContextInvocationHandler( + context, target)); + + } + + /* + * @see org.springframework.ldap.core.ContextSource#getReadWriteContext() + */ + public DirContext getReadWriteContext() throws NamingException { + DirContextHolder contextHolder = (DirContextHolder) TransactionSynchronizationManager + .getResource(target); + DirContext ctx = null; + + if (contextHolder != null) { + ctx = contextHolder.getCtx(); + } + + if (ctx == null) { + ctx = target.getReadWriteContext(); + if (contextHolder != null) { + contextHolder.setCtx(ctx); + } + } + return getTransactionAwareDirContextProxy(ctx, target); + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandler.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandler.java new file mode 100644 index 00000000..a72f5cb4 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandler.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.naming.directory.DirContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.NamingException; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.transaction.compensating.LdapTransactionUtils; +import org.springframework.transaction.compensating.support.CompensatingTransactionUtils; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Proxy implementation for DirContext, making sure that the instance is not + * closed during a transaction, and that all modifying operations are recorded, + * storing compensating rollback operations for them. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class TransactionAwareDirContextInvocationHandler implements + InvocationHandler { + + private static Log log = LogFactory + .getLog(TransactionAwareDirContextInvocationHandler.class); + + private DirContext target; + + private ContextSource contextSource; + + /** + * Constructor. + * + * @param target + * The target DirContext. + * @param contextSource + * The transactional ContextSource, needed to get hold of the + * current transaction's {@link DirContextHolder}. + */ + public TransactionAwareDirContextInvocationHandler(DirContext target, + ContextSource contextSource) { + this.target = target; + this.contextSource = contextSource; + } + + /* + * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, + * java.lang.reflect.Method, java.lang.Object[]) + */ + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + + String methodName = method.getName(); + if (methodName.equals("getTargetContext")) { + return target; + } else if (methodName.equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); + } else if (methodName.equals("hashCode")) { + // Use hashCode of Connection proxy. + return new Integer(hashCode()); + } else if (methodName.equals("close")) { + doCloseConnection(target, contextSource); + return null; + } else if (LdapTransactionUtils + .isSupportedWriteTransactionOperation(methodName)) { + // Store transaction data and allow operation to proceed. + CompensatingTransactionUtils.performOperation(contextSource, + target, method, args); + return null; + } else { + try { + return method.invoke(target, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + } + + /** + * Close the supplied context, but only if it is not associated with the + * current transaction. + * + * @param context + * the DirContext to close. + * @param contextSource + * the ContextSource bound to the transaction. + * @throws NamingException + */ + void doCloseConnection(DirContext context, ContextSource contextSource) + throws javax.naming.NamingException { + DirContextHolder transactionContextHolder = (DirContextHolder) TransactionSynchronizationManager + .getResource(contextSource); + if (transactionContextHolder == null + || transactionContextHolder.getCtx() != context) { + log.debug("Closing context"); + // This is not the transactional context or the transaction is + // no longer active - we should close it. + context.close(); + } else { + log.debug("Leaving transactional context open"); + } + } +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/package.html new file mode 100644 index 00000000..2cac0e87 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/manager/package.html @@ -0,0 +1,7 @@ + + + +The core implementation classes for client-side LDAP transactions. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/package.html new file mode 100644 index 00000000..a5d44d78 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/package.html @@ -0,0 +1,7 @@ + + + +LDAP specific implementations of the Compensating Transaction interfaces. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategy.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategy.java new file mode 100644 index 00000000..3b7bfb20 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategy.java @@ -0,0 +1,94 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.support; + +import java.util.List; + +import javax.naming.Name; + +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapRdn; +import org.springframework.ldap.core.LdapRdnComponent; +import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; + +/** + * Default implementation of {@link TempEntryRenamingStrategy}. This + * implementation simply adds "_temp" to the leftmost (least significant part) + * of the name. For example: + * + *
+ * cn=john doe, ou=company1, c=SE
+ * 
+ * + * becomes: + * + *
+ * cn=john doe_temp, ou=company1, c=SE
+ * 
+ *

+ * Note that using this strategy means that the entry remains in virtually the + * same location as where it originally resided. This means that searches later + * in the same transaction might return references to the temporary entry even + * though it should have been removed or rebound. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class DefaultTempEntryRenamingStrategy implements + TempEntryRenamingStrategy { + + /** + * The default temp entry suffix, "_temp". + */ + public static final String DEFAULT_TEMP_SUFFIX = "_temp"; + + private String tempSuffix = DEFAULT_TEMP_SUFFIX; + + /* + * @see org.springframework.ldap.support.transaction.TempEntryRenamingStrategy#getTemporaryName(javax.naming.Name) + */ + public Name getTemporaryName(Name originalName) { + DistinguishedName temporaryName = new DistinguishedName(originalName); + List names = temporaryName.getNames(); + LdapRdn rdn = (LdapRdn) names.get(names.size() - 1); + LdapRdnComponent rdnComponent = rdn.getComponent(); + String value = rdnComponent.getValue(); + rdnComponent.setValue(value + DEFAULT_TEMP_SUFFIX); + + return temporaryName; + } + + /** + * Get the suffix that will be used for renaming temporary entries. + * + * @return the suffix. + */ + public String getTempSuffix() { + return tempSuffix; + } + + /** + * Set the suffix to use for renaming temporary entries. Default value is + * {@link #DEFAULT_TEMP_SUFFIX}. + * + * @param tempSuffix + * the suffix. + */ + public void setTempSuffix(String tempSuffix) { + this.tempSuffix = tempSuffix; + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategy.java b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategy.java new file mode 100644 index 00000000..2513d234 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategy.java @@ -0,0 +1,89 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.support; + +import java.util.List; + +import javax.naming.Name; + +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapRdn; +import org.springframework.ldap.core.LdapRdnComponent; +import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; + +/** + * A {@link TempEntryRenamingStrategy} that moves the entry to a different + * subtree than the original entry. The specified subtree needs to be present in + * the LDAP tree; it will not be created and operations using this strategy will + * fail if the destination is not in place. However, this strategy is preferable + * to {@link DefaultTempEntryRenamingStrategy}, as it makes searches have the + * expected result even though the temporary entry still exists during the + * transaction. + *

+ * Example: If the specified subtreeNode is + * ou=tempEntries and the originalName is + * cn=john doe, ou=company1, c=SE, the result of + * {@link #getTemporaryName(Name)} will be + * cn=john doe1, ou=tempEntries. The "1" suffix is a + * sequence number needed to prevent potential collisions in the temporary + * storage. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class DifferentSubtreeTempEntryRenamingStrategy implements + TempEntryRenamingStrategy { + + private Name subtreeNode; + + private static int nextSequenceNo = 1; + + public DifferentSubtreeTempEntryRenamingStrategy(Name subtreeNode) { + this.subtreeNode = subtreeNode; + } + + public Name getSubtreeNode() { + return subtreeNode; + } + + public void setSubtreeNode(Name subtreeNode) { + this.subtreeNode = subtreeNode; + } + + int getNextSequenceNo() { + return nextSequenceNo; + } + + /* + * @see org.springframework.ldap.support.transaction.TempEntryRenamingStrategy#getTemporaryName(javax.naming.Name) + */ + public Name getTemporaryName(Name originalName) { + DistinguishedName tempName = new DistinguishedName(originalName); + List names = tempName.getNames(); + LdapRdn rdn = (LdapRdn) names.get(names.size() - 1); + LdapRdnComponent component = rdn.getComponent(); + + LdapRdn newRdn; + synchronized (this) { + newRdn = new LdapRdn(component.getKey(), component.getValue() + + nextSequenceNo++); + } + + DistinguishedName newName = new DistinguishedName(subtreeNode); + newName.add(newRdn); + return newName; + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/package.html b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/package.html new file mode 100644 index 00000000..b62d994f --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/ldap/transaction/compensating/support/package.html @@ -0,0 +1,7 @@ + + + +Useful helper implementations for client side Compensating LDAP Transactions. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationExecutor.java b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationExecutor.java new file mode 100644 index 00000000..afa196ad --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationExecutor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.transaction.compensating; + +/** + * Responsible for executing a single recorded operation as well as committing + * or rolling it back, depending on the transaction outcome. Instances of this + * interface are constructed by {@link CompensatingTransactionOperationRecorder} + * objects, supplying them with the information necessary for the respective + * operations. + *

+ * The actual operations performed by the respective methods of this class might + * not be what would originally be expected. E.g. one would expect that the + * {@link #performOperation()} method of a + * CompensatingTransactionOperationExecutor implementation would actually delete + * the entry, leaving it for the {@link #rollback()} method to recreate it using + * data from the original entry. However, this will not always be possible. In + * an LDAP system, for instance, it might not be possible to retrieve all the + * stored data from the original entry. In that case, the + * {@link #performOperation()} method will instead move the entry to a temporary + * location and leave it for the {@link #commit()} method to actually remove the + * entry. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public interface CompensatingTransactionOperationExecutor { + /** + * Rollback the operation, restoring state of the target as it was before + * the operation was performed using the information supplied on creation of + * this instance. + */ + public void rollback(); + + /** + * Commit the operation. In many cases, this will not require any work at + * all to be performed. However, in some cases there will be interesting + * stuff to do. See class description for elaboration on this. + */ + public void commit(); + + /** + * Perform the operation. This will most often require performing the + * recorded operation, but in some cases the actual operation performed by + * this method might be something else. See class description for + * elaboration on this. + */ + public void performOperation(); +} diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationFactory.java b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationFactory.java new file mode 100644 index 00000000..d494e14f --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.transaction.compensating; + +import org.springframework.transaction.compensating.support.DefaultCompensatingTransactionOperationManager; + +/** + * Factory interface for creating + * {@link CompensatingTransactionOperationRecorder} objects based on operation + * method names. + * + * @author Mattias Arthursson + * @see DefaultCompensatingTransactionOperationManager + * @since 1.2 + */ +public interface CompensatingTransactionOperationFactory { + /** + * Create an appropriate {@link CompensatingTransactionOperationRecorder} + * instance corresponding to the supplied method name. + * + * @param resource + * The target transaction resource. + * @param method + * the method name to create a + * {@link CompensatingTransactionOperationRecorder} for. + * + * @return a new {@link CompensatingTransactionOperationRecorder} instance. + */ + public CompensatingTransactionOperationRecorder createRecordingOperation( + Object resource, String method); +} diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationManager.java b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationManager.java new file mode 100644 index 00000000..5c117d8f --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationManager.java @@ -0,0 +1,55 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.transaction.compensating; + +/** + * A CompensatingTransactionOperationManager implementation records and performs + * operations that are to be performed within a compensating transaction. It + * keeps track of compensating actions necessary for rolling back each + * individual operation. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public interface CompensatingTransactionOperationManager { + /** + * Indicates that the supplied operation (method name) is to be performed. + * This method is responsible for recording the current state (prior to the + * operation), performing the operation, and storing the necessary + * information to roll back or commit the performed operation. + * + * @param resource + * the target resource to perform the operation on. + * @param operation + * The method to be invoked. + * @param args + * Arguments supplied to the method. + */ + public void performOperation(Object resource, String operation, + Object[] args); + + /** + * Rollback all recorded operations by performing each of the recorded + * rollback operations. + */ + public void rollback(); + + /** + * Commit all recorded operations. In many cases this means doing nothing, + * but in some cases some temporary data will need to be removed. + */ + public void commit(); +} diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationRecorder.java b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationRecorder.java new file mode 100644 index 00000000..9a9804bb --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/CompensatingTransactionOperationRecorder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.transaction.compensating; + +/** + * An implementation of this interface is responsible for recording data and + * supplying a {@link CompensatingTransactionOperationExecutor} to be invoked + * for execution and compensating transaction management of the operation. + * Recording of an operation should not fail (throwing an Exception), but + * instead log the result. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public interface CompensatingTransactionOperationRecorder { + /** + * Record information about the operation performed and return a + * corresponding {@link CompensatingTransactionOperationExecutor} to be used + * if the operation would need to be rolled back. + * + * @param args + * The arguments that have been sent to the operation. + * @return A {@link CompensatingTransactionOperationExecutor} to be used if + * the recorded operation should need to be rolled back. + */ + public CompensatingTransactionOperationExecutor recordOperation( + Object[] args); +} diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/package.html b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/package.html new file mode 100644 index 00000000..6d166abe --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/package.html @@ -0,0 +1,8 @@ + + + +Interface definitions for a general Compensating Transaction framework +based on PlatformTransactionManager. + + + diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java new file mode 100644 index 00000000..6288d2fe --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/AbstractCompensatingTransactionManagerDelegate.java @@ -0,0 +1,130 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.transaction.compensating.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.support.DefaultTransactionStatus; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Abstract superclass for Compensating TransactionManager delegates. The actual + * transaction work is extracted to a delegate to enable composite Transaction + * Managers. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public abstract class AbstractCompensatingTransactionManagerDelegate { + + private static Log log = LogFactory + .getLog(AbstractCompensatingTransactionManagerDelegate.class); + + /** + * Close the target resource - the implementation specific resource held in + * the specified {@link CompensatingTransactionHolderSupport}. + * + * @param transactionHolderSupport + * the {@link CompensatingTransactionHolderSupport} that holds + * the transaction specific target resource. + */ + protected abstract void closeTargetResource( + CompensatingTransactionHolderSupport transactionHolderSupport); + + /** + * Get a new implementation specific + * {@link CompensatingTransactionHolderSupport} instance. + * + * @return a new {@link CompensatingTransactionHolderSupport} instance. + */ + protected abstract CompensatingTransactionHolderSupport getNewHolder(); + + /** + * Get the key (normally, a DataSource or similar) that should be used for + * transaction synchronization. + * + * @return the transaction synchronization key + */ + protected abstract Object getTransactionSynchronizationKey(); + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction() + */ + public Object doGetTransaction() throws TransactionException { + CompensatingTransactionHolderSupport holder = (CompensatingTransactionHolderSupport) TransactionSynchronizationManager + .getResource(getTransactionSynchronizationKey()); + CompensatingTransactionObject txObject = new CompensatingTransactionObject( + holder); + return txObject; + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin(java.lang.Object, + * org.springframework.transaction.TransactionDefinition) + */ + public void doBegin(Object transaction, TransactionDefinition definition) + throws TransactionException { + CompensatingTransactionObject txObject = (CompensatingTransactionObject) transaction; + + if (txObject.getHolder() == null) { + CompensatingTransactionHolderSupport contextHolder = getNewHolder(); + txObject.setHolder(contextHolder); + + TransactionSynchronizationManager.bindResource( + getTransactionSynchronizationKey(), contextHolder); + } + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doCommit(org.springframework.transaction.support.DefaultTransactionStatus) + */ + public void doCommit(DefaultTransactionStatus status) + throws TransactionException { + CompensatingTransactionObject txObject = (CompensatingTransactionObject) status + .getTransaction(); + txObject.getHolder().getTransactionOperationManager().commit(); + + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus) + */ + public void doRollback(DefaultTransactionStatus status) + throws TransactionException { + CompensatingTransactionObject txObject = (CompensatingTransactionObject) status + .getTransaction(); + txObject.getHolder().getTransactionOperationManager().rollback(); + } + + /* + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doCleanupAfterCompletion(java.lang.Object) + */ + public void doCleanupAfterCompletion(Object transaction) { + log.debug("Cleaning stored transaction synchronization"); + TransactionSynchronizationManager + .unbindResource(getTransactionSynchronizationKey()); + + CompensatingTransactionObject txObject = (CompensatingTransactionObject) transaction; + CompensatingTransactionHolderSupport transactionHolderSupport = (CompensatingTransactionHolderSupport) txObject + .getHolder(); + + closeTargetResource(transactionHolderSupport); + + txObject.getHolder().clear(); + } +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionHolderSupport.java b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionHolderSupport.java new file mode 100644 index 00000000..de90a8e0 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionHolderSupport.java @@ -0,0 +1,80 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.transaction.compensating.support; + +import org.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import org.springframework.transaction.support.ResourceHolderSupport; + +/** + * Base class for compensating transaction resource holders. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public abstract class CompensatingTransactionHolderSupport extends + ResourceHolderSupport { + + private CompensatingTransactionOperationManager transactionOperationManager; + + /** + * Constructor. + * + * @param manager + * The {@link CompensatingTransactionOperationManager} to use for + * creating Compensating operations. + */ + public CompensatingTransactionHolderSupport( + CompensatingTransactionOperationManager manager) { + this.transactionOperationManager = manager; + } + + /** + * Get the actual transacted resource. + * + * @return the transaction's target resource + */ + protected abstract Object getTransactedResource(); + + /* + * @see org.springframework.transaction.support.ResourceHolderSupport#clear() + */ + public void clear() { + super.clear(); + transactionOperationManager = null; + } + + /** + * Get the CompensatingTransactionOperationManager to handle the data for + * the current transaction. + * + * @return the CompensatingTransactionOperationManager. + */ + public CompensatingTransactionOperationManager getTransactionOperationManager() { + return transactionOperationManager; + } + + /** + * Set the CompensatingTransactionOperationManager. For testing purposes + * only. + * + * @param transactionOperationManager + * the CompensatingTransactionOperationManager to use. + */ + public void setTransactionOperationManager( + CompensatingTransactionOperationManager transactionOperationManager) { + this.transactionOperationManager = transactionOperationManager; + } +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionObject.java b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionObject.java new file mode 100644 index 00000000..16dc4be7 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionObject.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.transaction.compensating.support; + +/** + * Transaction object used by + * {@link AbstractCompensatingTransactionManagerDelegate}. Keeps a reference to + * the {@link CompensatingTransactionHolderSupport} associated with the current + * transaction. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class CompensatingTransactionObject { + private CompensatingTransactionHolderSupport holder; + + /** + * Constructor. + * + * @param holder + * the {@link CompensatingTransactionHolderSupport} associated + * with the current transaction. + */ + public CompensatingTransactionObject( + CompensatingTransactionHolderSupport holder) { + this.holder = holder; + } + + /** + * Get the DirContextHolder. + * + * @return the DirContextHolder. + */ + public CompensatingTransactionHolderSupport getHolder() { + return holder; + } + + /** + * Set the {@link CompensatingTransactionHolderSupport} associated with the + * current transaction. + * + * @param holder + * the {@link CompensatingTransactionHolderSupport} associated + * with the current transaction. + */ + public void setHolder(CompensatingTransactionHolderSupport holder) { + this.holder = holder; + } +} \ No newline at end of file diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionUtils.java b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionUtils.java new file mode 100644 index 00000000..594d4e75 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/CompensatingTransactionUtils.java @@ -0,0 +1,76 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.transaction.compensating.support; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Common methods for use with compensating transactions. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class CompensatingTransactionUtils { + + /** + * Not to be instantiated. + */ + private CompensatingTransactionUtils() { + } + + /** + * Perform the specified operation, storing the state prior to the operation + * in order to enable commit/rollback later. If no transaction is currently + * active, proceed with the original call on the target. + * + * @param synchronizationKey + * the transaction synchronization key we are operating on + * (typically something similar to a DataSource). + * @param target + * the actual target resource that should be used for invoking + * the operation on should no transaction be active. + * @param method + * name of the method to be invoked. + * @param args + * arguments with which the operation is invoked. + */ + public static void performOperation(Object synchronizationKey, + Object target, Method method, Object[] args) throws Throwable { + CompensatingTransactionHolderSupport transactionResourceHolder = (CompensatingTransactionHolderSupport) TransactionSynchronizationManager + .getResource(synchronizationKey); + if (transactionResourceHolder != null) { + + CompensatingTransactionOperationManager transactionOperationManager = transactionResourceHolder + .getTransactionOperationManager(); + transactionOperationManager.performOperation( + transactionResourceHolder.getTransactedResource(), method + .getName(), args); + } else { + // Perform the target operation + try { + method.invoke(target, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + } + +} diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManager.java b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManager.java new file mode 100644 index 00000000..b947d970 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManager.java @@ -0,0 +1,128 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.transaction.compensating.support; + +import java.util.Iterator; +import java.util.Stack; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import org.springframework.transaction.compensating.CompensatingTransactionOperationFactory; +import org.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +/** + * Default implementation of {@link CompensatingTransactionOperationManager}. + * Manages a stack of {@link CompensatingTransactionOperationExecutor} objects + * and performs rollback of these in the reverse order. + * + * @author Mattias Arthursson + * @since 1.2 + */ +public class DefaultCompensatingTransactionOperationManager implements + CompensatingTransactionOperationManager { + + private static Log log = LogFactory + .getLog(DefaultCompensatingTransactionOperationManager.class); + + private Stack operationExecutors = new Stack(); + + private CompensatingTransactionOperationFactory operationFactory; + + /** + * Set the {@link CompensatingTransactionOperationFactory} to use. + * + * @param operationFactory + * the {@link CompensatingTransactionOperationFactory}. + */ + public DefaultCompensatingTransactionOperationManager( + CompensatingTransactionOperationFactory operationFactory) { + this.operationFactory = operationFactory; + } + + /* + * @see org.springframework.transaction.compensating.CompensatingTransactionOperationManager#performOperation(java.lang.Object, + * java.lang.String, java.lang.Object[]) + */ + public void performOperation(Object resource, String operation, + Object[] args) { + CompensatingTransactionOperationRecorder recorder = operationFactory + .createRecordingOperation(resource, operation); + CompensatingTransactionOperationExecutor executor = recorder + .recordOperation(args); + + executor.performOperation(); + + // Don't push the executor until the actual operation passed. + operationExecutors.push(executor); + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationManager#rollback() + */ + public void rollback() { + log.debug("Performing rollback"); + while (!operationExecutors.isEmpty()) { + CompensatingTransactionOperationExecutor rollbackOperation = (CompensatingTransactionOperationExecutor) operationExecutors + .pop(); + try { + rollbackOperation.rollback(); + } catch (Exception e) { + throw new TransactionSystemException( + "Error occurred during rollback", e); + } + } + } + + /** + * Get the rollback operations. Used for testing purposes. + * + * @return the rollback operations. + */ + protected Stack getOperationExecutors() { + return operationExecutors; + } + + /** + * Set the rollback operations. Package protected - for testing purposes + * only. + * + * @param operationExecutors + * the rollback operations. + */ + void setOperationExecutors(Stack operationExecutors) { + this.operationExecutors = operationExecutors; + } + + /* + * @see org.springframework.ldap.support.transaction.CompensatingTransactionOperationManager#commit() + */ + public void commit() { + log.debug("Performing rollback"); + for (Iterator iter = operationExecutors.iterator(); iter.hasNext();) { + CompensatingTransactionOperationExecutor operationExecutor = (CompensatingTransactionOperationExecutor) iter + .next(); + try { + operationExecutor.commit(); + } catch (Exception e) { + throw new TransactionSystemException( + "Error occurred during commit", e); + } + } + } +} diff --git a/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/package.html b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/package.html new file mode 100644 index 00000000..383254c7 --- /dev/null +++ b/mvn-build/core/src/main/java/org/springframework/transaction/compensating/support/package.html @@ -0,0 +1,9 @@ + + + +Support package for general Compensating Transaction Framework. +Contains default implementations of core interfaces as well as useful +helper classes. + + + diff --git a/mvn-build/core/src/main/java/overview.html b/mvn-build/core/src/main/java/overview.html new file mode 100644 index 00000000..fe97647c --- /dev/null +++ b/mvn-build/core/src/main/java/overview.html @@ -0,0 +1,3 @@ + +This document is the API specification for the Spring LDAP Framework. + \ No newline at end of file diff --git a/mvn-build/core/src/main/javacc/DnParserImpl.jj b/mvn-build/core/src/main/javacc/DnParserImpl.jj new file mode 100644 index 00000000..ff13ddf9 --- /dev/null +++ b/mvn-build/core/src/main/javacc/DnParserImpl.jj @@ -0,0 +1,154 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +options { + LOOKAHEAD = 1; + CHOICE_AMBIGUITY_CHECK = 3; + OTHER_AMBIGUITY_CHECK = 3; + STATIC = false; + DEBUG_PARSER = false; + DEBUG_LOOKAHEAD = false; + DEBUG_TOKEN_MANAGER = false; + ERROR_REPORTING = true; + JAVA_UNICODE_ESCAPE = false; + UNICODE_INPUT = true; + IGNORE_CASE = false; + USER_TOKEN_MANAGER = false; + USER_CHAR_STREAM = false; + BUILD_PARSER = true; + BUILD_TOKEN_MANAGER = true; + SANITY_CHECK = true; + FORCE_LA_CHECK = true; +} + +PARSER_BEGIN(DnParserImpl) + +package org.springframework.ldap.core; + +public class DnParserImpl implements DnParser{ + +} + +PARSER_END(DnParserImpl) + +TOKEN: { <#ALPHA: ["a"-"z", "A"-"Z"] > } +TOKEN: { <#DIGIT: ["0"-"9"]> } +TOKEN: { <#STRINGCHAR: ~[",","=","\r","+","<",">","#",";","\\","\""]> } +TOKEN: { <#STRINGENDCHAR: ~[",","=","\r","+","<",">","#",";","\\","\""," "]> } +TOKEN: { <#SPECIAL: [",","=","\r","+","<",">","#",";"]> } +TOKEN: { <#HEXCHAR: ["0"-"9","a"-"f","A"-"F"]> } +TOKEN: { <#HEXPAIR: > } +TOKEN: { <#BACKSLASHCHAR: "\\"> } +TOKEN: { <#PAIR: ( | | | )> } +TOKEN: { <#ESCAPEDSPACE: >} +TOKEN: { <#ESCAPEDSTART: ( | )> } +TOKEN: { <#STRINGEND: ( | | )> } + + TOKEN: +{ + + | + + | + ( | | "-")*> + | + )+ ("." ()+ )* > + | + +} + + TOKEN: +{ + ( | | )+ + | + ()+ + | + ()? ( | )* + )> +} + + TOKEN: +{ + )* "=" ()*> +} + +/** + * input -> dn + * dn -> rdn ( ( "," | ";" ) rdn )* + * rdn -> attributeTypeAndValue ( "+" attributeTypeAndValue )* + * attributeTypeAndValue -> + * ( )* AttributeType SpacedEquals AttributeValue ( )* + * SpacedEquals -> + * AttributeType -> | + * AttributeValue -> + */ +void input() : +{ +} +{ + dn() +} + +DistinguishedName dn(): +{ +DistinguishedName dn = new DistinguishedName(); +LdapRdn rdn; +} +{ + ( rdn = rdn() {dn.add(0, rdn);} (("," | ";") rdn = rdn() {dn.add(0, rdn);})* ) + {return dn;} +} + +LdapRdn rdn(): +{ +LdapRdnComponent rdnComponent; +LdapRdn rdn = new LdapRdn(); +} +{ + (rdnComponent = attributeTypeAndValue() {rdn.addComponent(rdnComponent);} ("+" rdnComponent = attributeTypeAndValue() {rdn.addComponent(rdnComponent);})*) + {return rdn;} +} + +LdapRdnComponent attributeTypeAndValue(): +{ +String attributeType; +String value; +} +{ + ( ()* attributeType = AttributeType() SpacedEquals() value = AttributeValue() ()*) + {return new LdapRdnComponent(attributeType, value, true);} +} + +void SpacedEquals(): +{} +{ + {token_source.SwitchTo(SPACED_EQUALS_S);} +} + +String AttributeType(): +{Token t;} +{ + ( t = | t = ) + {return t.image.toString();} +} + +String AttributeValue(): +{Token t;} +{ + {token_source.SwitchTo(ATTRVALUE_S);} t = {token_source.SwitchTo(DEFAULT);} + {return t.image.toString();} +} \ No newline at end of file diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/NamingExceptionTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/NamingExceptionTest.java new file mode 100644 index 00000000..8418c7ae --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/NamingExceptionTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import javax.naming.directory.InitialDirContext; + +import junit.framework.TestCase; + +/** + * Unit tests for the NamingException class. + * + * @author Ulrik Sandberg + */ +public class NamingExceptionTest extends TestCase { + private ByteArrayOutputStream byteArrayOutputStream; + + public void testNamingExceptionWithNonSerializableResolvedObj() + throws Exception { + javax.naming.NameAlreadyBoundException wrappedException = new javax.naming.NameAlreadyBoundException( + "some error"); + wrappedException.setResolvedObj(new InitialDirContext()); + NamingException exception = new NameAlreadyBoundException( + wrappedException); + writeToStream(exception); + NamingException deSerializedException = readFromStream(); + assertNotNull( + "Original exception resolvedObj after serialization should not be null", + exception.getResolvedObj()); + assertNull("De-serialized exception resolvedObj should be null", + deSerializedException.getResolvedObj()); + } + + private NamingException readFromStream() throws IOException, + ClassNotFoundException { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( + byteArrayOutputStream.toByteArray()); + ObjectInputStream in = new ObjectInputStream(byteArrayInputStream); + NamingException deSerializedException; + try { + deSerializedException = (NamingException) in.readObject(); + } finally { + in.close(); + } + return deSerializedException; + } + + private void writeToStream(NamingException exception) throws IOException { + byteArrayOutputStream = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream); + try { + out.writeObject(exception); + out.flush(); + } finally { + out.close(); + } + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/authentication/AcegiAuthenticationSourceTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/authentication/AcegiAuthenticationSourceTest.java new file mode 100644 index 00000000..dd9d89d4 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/authentication/AcegiAuthenticationSourceTest.java @@ -0,0 +1,144 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.authentication; + +import junit.framework.TestCase; + +import org.acegisecurity.Authentication; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; +import org.acegisecurity.userdetails.User; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; +import org.easymock.MockControl; +import org.springframework.ldap.authentication.AcegiAuthenticationSource; + +public class AcegiAuthenticationSourceTest extends TestCase { + + private MockControl authenticationControl; + + private Authentication authenticationMock; + + private MockControl ldapUserDetailsControl; + + private LdapUserDetails ldapUserDetailsMock; + + private AcegiAuthenticationSource tested; + + protected void setUp() throws Exception { + super.setUp(); + + authenticationControl = MockControl.createControl(Authentication.class); + authenticationMock = (Authentication) authenticationControl.getMock(); + + ldapUserDetailsControl = MockControl + .createControl(LdapUserDetails.class); + ldapUserDetailsMock = (LdapUserDetails) ldapUserDetailsControl + .getMock(); + + tested = new AcegiAuthenticationSource(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + authenticationControl = null; + authenticationMock = null; + + ldapUserDetailsControl = null; + ldapUserDetailsMock = null; + + tested = null; + } + + protected void replay() { + authenticationControl.replay(); + ldapUserDetailsControl.replay(); + } + + protected void verify() { + authenticationControl.verify(); + ldapUserDetailsControl.verify(); + } + + public void testGetPrincipalAndCredentials() { + authenticationControl.expectAndReturn( + authenticationMock.getPrincipal(), ldapUserDetailsMock); + authenticationControl.expectAndReturn(authenticationMock + .getCredentials(), "secret"); + + ldapUserDetailsControl.expectAndDefaultReturn(ldapUserDetailsMock + .getDn(), "cn=Manager"); + + SecurityContextHolder.getContext() + .setAuthentication(authenticationMock); + + replay(); + + String principal = tested.getPrincipal(); + String credentials = tested.getCredentials(); + + verify(); + + assertEquals("secret", credentials); + assertEquals("cn=Manager", principal); + } + + public void testGetPrincipalAndCredentials_nullAuthentication() { + SecurityContextHolder.getContext().setAuthentication(null); + + replay(); + + assertEquals("", tested.getPrincipal()); + assertEquals("", tested.getCredentials()); + verify(); + } + + public void testGetPrincipal_InvalidPrincipal() { + authenticationControl.expectAndReturn( + authenticationMock.getPrincipal(), new User("dummy", "dummy", + true, true, true, true, new GrantedAuthority[0])); + + SecurityContextHolder.getContext() + .setAuthentication(authenticationMock); + + replay(); + + try { + tested.getPrincipal(); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + verify(); + } + + public void testGetPrincipalWithAnonymousAuthenticationToken() { + + SecurityContextHolder.getContext().setAuthentication( + new AnonymousAuthenticationToken("dummy", "dummy", + new GrantedAuthority[] { new DummyAuthoroty() })); + + assertEquals("", tested.getPrincipal()); + } + + private static class DummyAuthoroty implements GrantedAuthority { + public String getAuthority() { + return null; + } + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecoratorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecoratorTest.java new file mode 100644 index 00000000..39596f7e --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/authentication/DefaultValuesAuthenticationSourceDecoratorTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.authentication; + +import org.easymock.MockControl; +import org.springframework.ldap.authentication.DefaultValuesAuthenticationSourceDecorator; +import org.springframework.ldap.core.AuthenticationSource; + +import junit.framework.TestCase; + +public class DefaultValuesAuthenticationSourceDecoratorTest extends TestCase { + + private static final String DEFAULT_PASSWORD = "defaultPassword"; + + private static final String DEFAULT_USER = "cn=defaultUser"; + + private DefaultValuesAuthenticationSourceDecorator tested; + + private MockControl authenticationSourceControl; + + private AuthenticationSource authenticationSourceMock; + + protected void setUp() throws Exception { + super.setUp(); + + authenticationSourceControl = MockControl + .createControl(AuthenticationSource.class); + authenticationSourceMock = (AuthenticationSource) authenticationSourceControl + .getMock(); + tested = new DefaultValuesAuthenticationSourceDecorator(); + tested.setDefaultUser(DEFAULT_USER); + tested.setDefaultPassword(DEFAULT_PASSWORD); + tested.setTarget(authenticationSourceMock); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + tested = null; + authenticationSourceControl = null; + authenticationSourceMock = null; + } + + public void testGetPrincipal_TargetHasPrincipal() { + authenticationSourceControl.expectAndDefaultReturn( + authenticationSourceMock.getPrincipal(), "cn=someUser"); + authenticationSourceControl.replay(); + + String principal = tested.getPrincipal(); + + authenticationSourceControl.verify(); + assertEquals("cn=someUser", principal); + } + + public void testGetPrincipal_TargetHasNoPrincipal() { + authenticationSourceControl.expectAndDefaultReturn( + authenticationSourceMock.getPrincipal(), ""); + authenticationSourceControl.replay(); + + String principal = tested.getPrincipal(); + + authenticationSourceControl.verify(); + assertEquals(DEFAULT_USER, principal); + } + + public void testGetCredentials_TargetHasPrincipal() { + authenticationSourceControl.expectAndDefaultReturn( + authenticationSourceMock.getPrincipal(), "cn=someUser"); + authenticationSourceControl.expectAndDefaultReturn( + authenticationSourceMock.getCredentials(), "somepassword"); + authenticationSourceControl.replay(); + + String credentials = tested.getCredentials(); + + authenticationSourceControl.verify(); + assertEquals("somepassword", credentials); + } + + public void testGetCredentials_TargetHasNoPrincipal() { + authenticationSourceControl.expectAndDefaultReturn( + authenticationSourceMock.getPrincipal(), ""); + authenticationSourceControl.expectAndDefaultReturn( + authenticationSourceMock.getCredentials(), "somepassword"); + authenticationSourceControl.replay(); + + String credentials = tested.getCredentials(); + + authenticationSourceControl.verify(); + assertEquals(DEFAULT_PASSWORD, credentials); + } + + public void testAfterPropertiesSet_noTarget() throws Exception { + tested.setTarget(null); + try { + tested.afterPropertiesSet(); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testAfterPropertiesSet_noDefaultUser() throws Exception { + tested.setDefaultUser(null); + try { + tested.afterPropertiesSet(); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testAfterPropertiesSet_noDefaultPassword() throws Exception { + tested.setDefaultPassword(null); + try { + tested.afterPropertiesSet(); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultTest.java new file mode 100644 index 00000000..e42aa115 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.control; + +import java.util.LinkedList; +import java.util.List; + +import org.springframework.ldap.control.PagedResult; +import org.springframework.ldap.control.PagedResultsCookie; + +import com.gargoylesoftware.base.testing.EqualsTester; + +import junit.framework.TestCase; + +public class PagedResultTest extends TestCase { + public void testEquals() throws Exception { + List expectedList = new LinkedList(); + expectedList.add("dummy"); + List otherList = new LinkedList(); + otherList.add("different"); + + PagedResult originalObject = new PagedResult(expectedList, + new PagedResultsCookie(null)); + PagedResult identicalObject = new PagedResult(expectedList, + new PagedResultsCookie(null)); + PagedResult differentObject = new PagedResult(otherList, + new PagedResultsCookie(null)); + PagedResult subclassObject = new PagedResult(expectedList, + new PagedResultsCookie(null)) { + + }; + + new EqualsTester(originalObject, identicalObject, differentObject, + subclassObject); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultsCookieTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultsCookieTest.java new file mode 100644 index 00000000..07566e35 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultsCookieTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.control; + +import org.springframework.ldap.control.PagedResultsCookie; + +import com.gargoylesoftware.base.testing.EqualsTester; + +import junit.framework.TestCase; + +public class PagedResultsCookieTest extends TestCase { + public void testEquals() { + byte[] expectedCookie = new byte[] { 1, 2 }; + byte[] differentCookie = new byte[] { 2, 3 }; + + PagedResultsCookie originalObject = new PagedResultsCookie( + expectedCookie); + PagedResultsCookie identicalObject = new PagedResultsCookie( + expectedCookie); + PagedResultsCookie differentObject = new PagedResultsCookie( + differentCookie); + PagedResultsCookie subclassObject = new PagedResultsCookie( + expectedCookie) { + + }; + + new EqualsTester(originalObject, identicalObject, differentObject, + subclassObject); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultsRequestControlTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultsRequestControlTest.java new file mode 100644 index 00000000..0b72e5a1 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/control/PagedResultsRequestControlTest.java @@ -0,0 +1,207 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.control; + +import java.io.IOException; + +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.control.PagedResultsCookie; +import org.springframework.ldap.control.PagedResultsRequestControl; + +import com.sun.jndi.ldap.Ber; +import com.sun.jndi.ldap.BerDecoder; +import com.sun.jndi.ldap.BerEncoder; +import com.sun.jndi.ldap.ctl.DirSyncResponseControl; +import com.sun.jndi.ldap.ctl.PagedResultsControl; +import com.sun.jndi.ldap.ctl.PagedResultsResponseControl; + +public class PagedResultsRequestControlTest extends TestCase { + + private MockControl ldapContextControl; + + private LdapContext ldapContextMock; + + protected void setUp() throws Exception { + super.setUp(); + + // Create ldapContext mock + ldapContextControl = MockControl.createControl(LdapContext.class); + ldapContextMock = (LdapContext) ldapContextControl.getMock(); + + } + + protected void tearDown() throws Exception { + super.tearDown(); + + ldapContextControl = null; + ldapContextMock = null; + + } + + protected void replay() { + ldapContextControl.replay(); + } + + protected void verify() { + ldapContextControl.verify(); + } + + public void testCreateRequestControl() throws Exception { + PagedResultsRequestControl tested = new PagedResultsRequestControl(20); + + PagedResultsControl control = (PagedResultsControl) tested + .createRequestControl(); + assertNotNull(control); + } + + public void testCreateRequestControl_CookieSet() throws Exception { + PagedResultsCookie cookie = new PagedResultsCookie(new byte[0]); + PagedResultsRequestControl tested = new PagedResultsRequestControl(20, + cookie); + + PagedResultsControl control = (PagedResultsControl) tested + .createRequestControl(); + assertNotNull(control); + } + + public void testPostProcess() throws Exception { + int resultSize = 50; + byte pageSize = 8; + + byte[] value = new byte[1]; + value[0] = pageSize; + byte[] cookie = encodeValue(resultSize, value); + PagedResultsResponseControl control = new PagedResultsResponseControl( + "dummy", true, cookie); + + ldapContextControl.expectAndDefaultReturn(ldapContextMock + .getResponseControls(), new Control[] { control }); + + PagedResultsRequestControl tested = new PagedResultsRequestControl(20); + + replay(); + + tested.postProcess(ldapContextMock); + + verify(); + + PagedResultsCookie returnedCookie = tested.getCookie(); + assertEquals(8, returnedCookie.getCookie()[0]); + assertEquals(20, tested.getPageSize()); + assertEquals(50, tested.getResultSize()); + } + + public void testPostProcess_InvalidResponseControl() throws Exception { + int resultSize = 50; + byte pageSize = 8; + + byte[] value = new byte[1]; + value[0] = pageSize; + byte[] cookie = encodeDirSyncValue(resultSize, value); + + // Using another response control to verify that it is ignored + DirSyncResponseControl control = new DirSyncResponseControl( + "dummy", true, cookie); + + ldapContextControl.expectAndDefaultReturn(ldapContextMock + .getResponseControls(), new Control[] { control }); + + PagedResultsRequestControl tested = new PagedResultsRequestControl(20); + + replay(); + + tested.postProcess(ldapContextMock); + + verify(); + + assertNull(tested.getCookie()); + assertEquals(20, tested.getPageSize()); + assertEquals(0, tested.getResultSize()); + } + + public void testPostProcess_NoResponseControls() throws Exception { + ldapContextControl.expectAndDefaultReturn(ldapContextMock + .getResponseControls(), null); + + PagedResultsRequestControl tested = new PagedResultsRequestControl(20); + + replay(); + + tested.postProcess(ldapContextMock); + + verify(); + + assertNull(tested.getCookie()); + assertEquals(20, tested.getPageSize()); + assertEquals(0, tested.getResultSize()); + } + + public void testBerDecoding() throws Exception { + byte[] value = new byte[1]; + value[0] = 8; + int pageSize = 20; + byte[] cookie = encodeValue(pageSize, value); + + BerDecoder ber = new BerDecoder(cookie, 0, cookie.length); + + ber.parseSeq(null); + int actualPageSize = ber.parseInt(); + byte[] actualValue = ber.parseOctetString(Ber.ASN_OCTET_STR, null); + + assertEquals("pageSize,", 20, actualPageSize); + assertEquals("value length", value.length, actualValue.length); + for (int i = 0; i < value.length; i++) { + assertEquals("value (index " + i + "),", value[i], actualValue[i]); + } + } + + private byte[] encodeValue(int pageSize, byte[] cookie) + throws IOException { + + // build the ASN.1 encoding + BerEncoder ber = new BerEncoder(10 + cookie.length); + + ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); + ber.encodeInt(pageSize); + ber.encodeOctetString(cookie, Ber.ASN_OCTET_STR); + ber.endSeq(); + + return ber.getTrimmedBuf(); + } + + /** + * Encode a value suitable for the DirSyncResponseControl used in a test. + */ + private byte[] encodeDirSyncValue(int pageSize, byte[] cookie) + throws IOException { + + // build the ASN.1 encoding + BerEncoder ber = new BerEncoder(10 + cookie.length); + + ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); + ber.encodeInt(1); // flag + ber.encodeInt(pageSize); // maxReturnLength + ber.encodeOctetString(cookie, Ber.ASN_OCTET_STR); + ber.endSeq(); + + return ber.getTrimmedBuf(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/control/RequestControlDirContextProcessorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/control/RequestControlDirContextProcessorTest.java new file mode 100644 index 00000000..edaa3b48 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/control/RequestControlDirContextProcessorTest.java @@ -0,0 +1,159 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.control; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.control.AbstractRequestControlDirContextProcessor; + +public class RequestControlDirContextProcessorTest extends TestCase { + + private AbstractRequestControlDirContextProcessor tested; + + private MockControl requestControlControl; + + private Control requestControlMock; + + private MockControl requestControl2Control; + + private Control requestControl2Mock; + + private MockControl ldapContextControl; + + private LdapContext ldapContextMock; + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + protected void setUp() throws Exception { + super.setUp(); + // Create requestControl mock + requestControlControl = MockControl.createControl(Control.class); + requestControlMock = (Control) requestControlControl.getMock(); + + // Create requestControl2 mock + requestControl2Control = MockControl.createControl(Control.class); + requestControl2Mock = (Control) requestControl2Control.getMock(); + + // Create ldapContext mock + ldapContextControl = MockControl.createControl(LdapContext.class); + ldapContextMock = (LdapContext) ldapContextControl.getMock(); + + // Create dirContext mock + dirContextControl = MockControl.createControl(DirContext.class); + dirContextMock = (DirContext) dirContextControl.getMock(); + + tested = new AbstractRequestControlDirContextProcessor() { + + public Control createRequestControl() { + return requestControlMock; + } + + public void postProcess(DirContext ctx) throws NamingException { + } + + }; + } + + protected void tearDown() throws Exception { + super.tearDown(); + + requestControlControl = null; + requestControlMock = null; + + requestControl2Control = null; + requestControl2Mock = null; + + ldapContextControl = null; + ldapContextMock = null; + + dirContextControl = null; + dirContextMock = null; + + } + + protected void replay() { + requestControlControl.replay(); + requestControl2Control.replay(); + ldapContextControl.replay(); + dirContextControl.replay(); + } + + protected void verify() { + requestControlControl.verify(); + requestControl2Control.verify(); + ldapContextControl.verify(); + dirContextControl.verify(); + } + + public void testPreProcess() throws NamingException { + ldapContextControl.setDefaultMatcher(MockControl.ARRAY_MATCHER); + ldapContextControl.expectAndDefaultReturn(ldapContextMock + .getRequestControls(), new Control[] { requestControl2Mock }); + ldapContextMock.setRequestControls(new Control[] { requestControl2Mock, + requestControlMock }); + + replay(); + + tested.preProcess(ldapContextMock); + + verify(); + } + + public void testPreProcess_NoExistingControls() throws NamingException { + ldapContextControl.setDefaultMatcher(MockControl.ARRAY_MATCHER); + ldapContextControl.expectAndDefaultReturn(ldapContextMock + .getRequestControls(), new Control[0]); + ldapContextMock + .setRequestControls(new Control[] { requestControlMock }); + + replay(); + + tested.preProcess(ldapContextMock); + + verify(); + } + + public void testPreProcess_NullControls() throws NamingException { + ldapContextControl.setDefaultMatcher(MockControl.ARRAY_MATCHER); + ldapContextControl.expectAndDefaultReturn(ldapContextMock + .getRequestControls(), null); + ldapContextMock + .setRequestControls(new Control[] { requestControlMock }); + + replay(); + + tested.preProcess(ldapContextMock); + + verify(); + } + + public void testPreProcess_NotLdapContext() throws Exception { + try { + tested.preProcess(dirContextMock); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/control/SortControlDirContextProcessorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/control/SortControlDirContextProcessorTest.java new file mode 100644 index 00000000..fe9e5673 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/control/SortControlDirContextProcessorTest.java @@ -0,0 +1,193 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.control; + +import java.io.IOException; + +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; +import javax.naming.ldap.PagedResultsControl; + +import junit.framework.TestCase; + +import org.easymock.MockControl; + +import com.sun.jndi.ldap.Ber; +import com.sun.jndi.ldap.BerDecoder; +import com.sun.jndi.ldap.BerEncoder; +import com.sun.jndi.ldap.ctl.DirSyncResponseControl; +import com.sun.jndi.ldap.ctl.SortControl; +import com.sun.jndi.ldap.ctl.SortResponseControl; + +/** + * Unit tests for the SortControlDirContextProcessor class. + * {@link javax.naming.ldap.SortControl} + * {@link javax.naming.ldap.SortResponseControl} + * {@link PagedResultsControl} + * + * @author Ulrik Sandberg + */ +public class SortControlDirContextProcessorTest extends TestCase { + + private MockControl ldapContextControl; + + private LdapContext ldapContextMock; + + protected void setUp() throws Exception { + super.setUp(); + + // Create ldapContext mock + ldapContextControl = MockControl.createControl(LdapContext.class); + ldapContextMock = (LdapContext) ldapContextControl.getMock(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + ldapContextControl = null; + ldapContextMock = null; + } + + protected void replay() { + ldapContextControl.replay(); + } + + protected void verify() { + ldapContextControl.verify(); + } + + public void testCreateRequestControl() throws Exception { + SortControlDirContextProcessor tested = new SortControlDirContextProcessor( + "key"); + + SortControl result = (SortControl) tested.createRequestControl(); + assertNotNull(result); + assertEquals("1.2.840.113556.1.4.473", result.getID()); + assertEquals(9, result.getEncodedValue().length); + } + + public void testPostProcess() throws Exception { + byte sortResult = 0; // success + + byte[] value = encodeValue(sortResult); + SortResponseControl control = new SortResponseControl( + "dummy", true, value); + + ldapContextControl.expectAndDefaultReturn(ldapContextMock + .getResponseControls(), new Control[] { control }); + + SortControlDirContextProcessor tested = new SortControlDirContextProcessor("key"); + + replay(); + + tested.postProcess(ldapContextMock); + + verify(); + + assertEquals(true, tested.isSorted()); + assertEquals(0, tested.getResultCode()); + } + + public void testPostProcess_NonSuccess() throws Exception { + byte sortResult = 1; + + byte[] value = encodeValue(sortResult); + SortResponseControl control = new SortResponseControl( + "dummy", true, value); + + ldapContextControl.expectAndDefaultReturn(ldapContextMock + .getResponseControls(), new Control[] { control }); + + SortControlDirContextProcessor tested = new SortControlDirContextProcessor("key"); + + replay(); + + tested.postProcess(ldapContextMock); + + verify(); + + assertEquals(false, tested.isSorted()); + assertEquals(1, tested.getResultCode()); + } + + public void testPostProcess_InvalidResponseControl() throws Exception { + int resultSize = 50; + byte pageSize = 8; + + byte[] value = new byte[1]; + value[0] = pageSize; + byte[] cookie = encodeDirSyncValue(resultSize, value); + + // Using another response control to verify that it is ignored + DirSyncResponseControl control = new DirSyncResponseControl("dummy", + true, cookie); + + ldapContextControl.expectAndDefaultReturn(ldapContextMock + .getResponseControls(), new Control[] { control }); + + SortControlDirContextProcessor tested = new SortControlDirContextProcessor("key"); + + replay(); + + tested.postProcess(ldapContextMock); + + verify(); + + assertEquals(false, tested.isSorted()); + } + + public void testBerDecoding() throws Exception { + int sortResult = 53; // unwilling to perform + byte[] encoded = encodeValue(sortResult); + + BerDecoder ber = new BerDecoder(encoded, 0, encoded.length); + + ber.parseSeq(null); + int actualSortResult = ber.parseEnumeration(); + + assertEquals("sortResult,", 53, actualSortResult); + } + + private byte[] encodeValue(int sortResult) throws IOException { + + // build the ASN.1 encoding + BerEncoder ber = new BerEncoder(10); + + ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); + ber.encodeInt(sortResult, Ber.ASN_ENUMERATED); + ber.endSeq(); + + return ber.getTrimmedBuf(); + } + + /** + * Encode a value suitable for the DirSyncResponseControl used in a test. + */ + private byte[] encodeDirSyncValue(int pageSize, byte[] cookie) + throws IOException { + + // build the ASN.1 encoding + BerEncoder ber = new BerEncoder(10 + cookie.length); + + ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); + ber.encodeInt(1); // flag + ber.encodeInt(pageSize); // maxReturnLength + ber.encodeOctetString(cookie, Ber.ASN_OCTET_STR); + ber.endSeq(); + + return ber.getTrimmedBuf(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/CollectingNameClassPairCallbackHandlerTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/CollectingNameClassPairCallbackHandlerTest.java new file mode 100644 index 00000000..4e8bf080 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/CollectingNameClassPairCallbackHandlerTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import java.util.List; + +import javax.naming.NameClassPair; + +import org.springframework.ldap.core.CollectingNameClassPairCallbackHandler; + +import junit.framework.TestCase; + +public class CollectingNameClassPairCallbackHandlerTest extends TestCase { + + private CollectingNameClassPairCallbackHandler tested; + + private Object expectedResult; + + private NameClassPair expectedNameClassPair; + + protected void setUp() throws Exception { + super.setUp(); + + expectedResult = new Object(); + expectedNameClassPair = new NameClassPair(null, null); + tested = new CollectingNameClassPairCallbackHandler() { + public Object getObjectFromNameClassPair(NameClassPair nameClassPair) { + assertSame(expectedNameClassPair, nameClassPair); + return expectedResult; + } + }; + } + + protected void tearDown() throws Exception { + super.tearDown(); + + expectedNameClassPair = null; + expectedResult = null; + tested = null; + } + + public void testHandleNameClassPair() { + tested.handleNameClassPair(expectedNameClassPair); + List result = tested.getList(); + assertEquals(1, result.size()); + assertSame(expectedResult, result.get(0)); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/ContextMapperCallbackHandlerTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/ContextMapperCallbackHandlerTest.java new file mode 100644 index 00000000..48158bd2 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/ContextMapperCallbackHandlerTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import javax.naming.Binding; + +import org.easymock.MockControl; + +import junit.framework.TestCase; + +public class ContextMapperCallbackHandlerTest extends TestCase { + + private MockControl mapperControl; + + private ContextMapper mapperMock; + + private ContextMapperCallbackHandler tested; + + protected void setUp() throws Exception { + super.setUp(); + + mapperControl = MockControl.createControl(ContextMapper.class); + mapperMock = (ContextMapper) mapperControl.getMock(); + tested = new ContextMapperCallbackHandler(mapperMock); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + mapperControl = null; + mapperMock = null; + tested = null; + } + + public void testConstructorWithEmptyArgument() { + try { + new ContextMapperCallbackHandler(null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testGetObjectFromNameClassPair() { + Object expectedObject = "object"; + Object expectedResult = "result"; + Binding expectedBinding = new Binding("some name", expectedObject); + + mapperControl.expectAndReturn( + mapperMock.mapFromContext(expectedObject), expectedResult); + + mapperControl.replay(); + + Object actualResult = tested + .getObjectFromNameClassPair(expectedBinding); + + mapperControl.verify(); + + assertEquals(expectedResult, actualResult); + } + + public void testGetObjectFromNameClassPairObjectRetrievalException() { + Binding expectedBinding = new Binding("some name", null); + + mapperControl.replay(); + + try { + tested.getObjectFromNameClassPair(expectedBinding); + fail("ObjectRetrievalException expected"); + } catch (ObjectRetrievalException expected) { + assertTrue(true); + } + mapperControl.verify(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/DirContextAdapterBugTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/DirContextAdapterBugTest.java new file mode 100644 index 00000000..96289f2b --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/DirContextAdapterBugTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import javax.naming.Name; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; + +import junit.framework.TestCase; + + +/** + * Unit tests that serve as regression tests for bugs that have been fixed. + * + * @author Luke Taylor + */ +public class DirContextAdapterBugTest extends TestCase { + + public void testResetAttributeValuesNotReportedAsModifications() { + BasicAttributes attrs = new BasicAttributes("myattr", "a"); + attrs.get("myattr").add("b"); + attrs.get("myattr").add("c"); + UpdateAdapter ctx = new UpdateAdapter(attrs, new DistinguishedName()); + + ctx.setAttributeValues("myattr", new String[] { "a", "b" }); + ctx.setAttributeValues("myattr", new String[] { "a", "b", "c" }); + + assertEquals(0, ctx.getModificationItems().length); + } + + public void testResetAttributeValuesSameLengthNotReportedAsModifications() { + BasicAttributes attrs = new BasicAttributes("myattr", "a"); + attrs.get("myattr").add("b"); + attrs.get("myattr").add("c"); + UpdateAdapter ctx = new UpdateAdapter(attrs, new DistinguishedName()); + + ctx.setAttributeValues("myattr", new String[] { "a", "b", "d" }); + ctx.setAttributeValues("myattr", new String[] { "a", "b", "c" }); + + assertEquals(0, ctx.getModificationItems().length); + } + + /** + * This test starts with an array with a null value in it (because that's + * how BasicAttributes will do it), changes to [a], and then + * changes to null. The current code interprets this as a + * change and will replace the original array with an empty array. + * + * TODO Is this correct behaviour? + */ + public void testResetNullAttributeValuesReportedAsModifications() { + BasicAttributes attrs = new BasicAttributes("myattr", null); + UpdateAdapter ctx = new UpdateAdapter(attrs, new DistinguishedName()); + + ctx.setAttributeValues("myattr", new String[] { "a" }); + ctx.setAttributeValues("myattr", null); + + assertEquals(1, ctx.getModificationItems().length); + } + + public void testResetNullAttributeValueNotReportedAsModification() throws Exception { + BasicAttributes attrs = new BasicAttributes("myattr", "b"); + UpdateAdapter ctx = new UpdateAdapter(attrs, new DistinguishedName()); + + ctx.setAttributeValue("myattr", "a"); + ctx.setAttributeValue("myattr", "b"); + + assertEquals(0, ctx.getModificationItems().length); + } + + private static class UpdateAdapter extends DirContextAdapter { + public UpdateAdapter(Attributes attrs, Name dn) { + super(attrs, dn); + setUpdateMode(true); + } + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/DirContextAdapterTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/DirContextAdapterTest.java new file mode 100644 index 00000000..526381c3 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/DirContextAdapterTest.java @@ -0,0 +1,1005 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.Name; +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.DirContext; +import javax.naming.directory.ModificationItem; + +import junit.framework.TestCase; + +/** + * Tests the DirContextAdapter class. + * + * @author Andreas Ronge + * @author Mattias Arthursson + */ +public class DirContextAdapterTest extends TestCase { + private static final DistinguishedName BASE_NAME = new DistinguishedName( + "dc=jayway, dc=se"); + + private static final DistinguishedName DUMMY_NAME = new DistinguishedName( + "c=SE, dc=jayway, dc=se"); + + private DirContextAdapter tested; + + protected void setUp() throws Exception { + super.setUp(); + tested = new DirContextAdapter(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testSetUpdateMode() throws Exception { + assertFalse(tested.isUpdateMode()); + tested.setUpdateMode(true); + assertTrue(tested.isUpdateMode()); + tested.setUpdateMode(false); + assertFalse(tested.isUpdateMode()); + } + + public void testGetModificationItems() throws Exception { + ModificationItem[] items = tested.getModificationItems(); + assertEquals(0, items.length); + tested.setUpdateMode(true); + assertEquals(0, items.length); + } + + public void testAlwaysReplace() throws Exception { + ModificationItem[] items = tested.getModificationItems(); + assertEquals(0, items.length); + tested.setUpdateMode(true); + assertEquals(0, items.length); + } + + public void testGetStringAttributeNotExists() throws Exception { + String s = tested.getStringAttribute("does not exist"); + assertNull(s); + } + + public void testGetStringAttributeExists() throws Exception { + final Attributes attrs = new BasicAttributes(); + attrs.put(new BasicAttribute("abc", "def")); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(attrs, null); + } + } + tested = new TestableDirContextAdapter(); + String s = tested.getStringAttribute("abc"); + assertEquals("def", s); + } + + public void testGetStringAttributesExists() throws Exception { + final Attributes attrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("234"); + attrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(attrs, null); + } + } + tested = new TestableDirContextAdapter(); + String s[] = tested.getStringAttributes("abc"); + assertEquals("123", s[0]); + assertEquals("234", s[1]); + assertEquals(2, s.length); + } + + public void testGetStringAttributesNotExists() throws Exception { + String s[] = tested.getStringAttributes("abc"); + assertNull(s); + } + + public void testAddAttributeValue() throws NamingException { + // Perform test + tested.addAttributeValue("abc", "123"); + + Attributes attrs = tested.getAttributes(); + Attribute attr = attrs.get("abc"); + assertEquals("123", (String) attr.get()); + } + + public void testAddAttributeValueAttributeWithOtherValueExists() + throws NamingException { + tested.setAttribute(new BasicAttribute("abc", "321")); + + // Perform test + tested.addAttributeValue("abc", "123"); + + Attributes attrs = tested.getAttributes(); + Attribute attr = attrs.get("abc"); + assertEquals("321", (String) attr.get(0)); + assertEquals("123", (String) attr.get(1)); + } + + public void testAddAttributeValueAttributeWithSameValueExists() + throws NamingException { + tested.setAttribute(new BasicAttribute("abc", "123")); + + // Perform test + tested.addAttributeValue("abc", "123"); + + Attributes attrs = tested.getAttributes(); + Attribute attr = attrs.get("abc"); + assertEquals(1, attr.size()); + assertEquals("123", (String) attr.get(0)); + } + + public void testAddAttributeValueInUpdateMode() throws NamingException { + tested.setUpdateMode(true); + tested.addAttributeValue("abc", "123"); + + // Perform test + Attributes attrs = tested.getAttributes(); + assertNull(attrs.get("abc")); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + Attribute attribute = modificationItems[0].getAttribute(); + assertEquals("abc", attribute.getID()); + assertEquals("123", attribute.get()); + } + + public void testAddAttributeValueInUpdateModeAttributeWithOtherValueExistsInOrigAttrs() + throws NamingException { + + tested.setAttribute(new BasicAttribute("abc", "321")); + tested.setUpdateMode(true); + + // Perform test + tested.addAttributeValue("abc", "123"); + + Attributes attrs = tested.getAttributes(); + assertNotNull(attrs.get("abc")); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + Attribute attribute = modificationItems[0].getAttribute(); + assertEquals(1, attribute.size()); + assertEquals("abc", attribute.getID()); + assertEquals("123", attribute.get()); + } + + public void testAddAttributeValueInUpdateModeAttributeWithSameValueExistsInOrigAttrs() + throws NamingException { + + tested.setAttribute(new BasicAttribute("abc", "123")); + tested.setUpdateMode(true); + + // Perform test + tested.addAttributeValue("abc", "123"); + + Attributes attrs = tested.getAttributes(); + assertNotNull(attrs.get("abc")); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(0, modificationItems.length); + } + + public void testAddAttributeValueInUpdateModeAttributeWithOtherValueExistsInUpdAttrs() + throws NamingException { + tested.setUpdateMode(true); + tested.setAttributeValue("abc", "321"); + + // Perform test + tested.addAttributeValue("abc", "123"); + + Attributes attrs = tested.getAttributes(); + assertNull(attrs.get("abc")); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + Attribute attribute = modificationItems[0].getAttribute(); + assertEquals("abc", attribute.getID()); + assertEquals("321", attribute.get(0)); + assertEquals("123", attribute.get(1)); + } + + public void testAddAttributeValueInUpdateModeAttributeWithSameValueExistsInUpdAttrs() + throws NamingException { + tested.setUpdateMode(true); + tested.setAttributeValue("abc", "123"); + + // Perform test + tested.addAttributeValue("abc", "123"); + + Attributes attrs = tested.getAttributes(); + assertNull(attrs.get("abc")); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + Attribute attribute = modificationItems[0].getAttribute(); + assertEquals(1, attribute.size()); + assertEquals("abc", attribute.getID()); + assertEquals("123", attribute.get()); + } + + public void testRemoveAttributeValueAttributeDoesntExist() { + // Perform test + tested.removeAttributeValue("abc", "123"); + + Attributes attributes = tested.getAttributes(); + assertNull(attributes.get("abc")); + } + + public void testRemoveAttributeValueAttributeWithOtherValueExists() + throws NamingException { + tested.setAttribute(new BasicAttribute("abc", "321")); + + // Perform test + tested.removeAttributeValue("abc", "123"); + + Attributes attributes = tested.getAttributes(); + Attribute attr = attributes.get("abc"); + assertNotNull(attr); + assertEquals(1, attr.size()); + assertEquals("321", attr.get()); + } + + public void testRemoveAttributeValueAttributeWithSameValueExists() { + tested.setAttribute(new BasicAttribute("abc", "123")); + + // Perform test + tested.removeAttributeValue("abc", "123"); + + Attributes attributes = tested.getAttributes(); + Attribute attr = attributes.get("abc"); + assertNull(attr); + } + + public void testRemoveAttributeValueAttributeWithOtherAndSameValueExists() + throws NamingException { + BasicAttribute basicAttribute = new BasicAttribute("abc"); + basicAttribute.add("123"); + basicAttribute.add("321"); + tested.setAttribute(basicAttribute); + + // Perform test + tested.removeAttributeValue("abc", "123"); + + Attributes attributes = tested.getAttributes(); + Attribute attr = attributes.get("abc"); + assertNotNull(attr); + assertEquals(1, attr.size()); + assertEquals("321", attr.get()); + } + + public void testRemoveAttributeValueInUpdateMode() { + tested.setUpdateMode(true); + + // Perform test + tested.removeAttributeValue("abc", "123"); + + assertNull(tested.getAttributes().get("abc")); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(0, modificationItems.length); + } + + public void testRemoveAttributeValueInUpdateModeSameValueExistsInUpdatedAttrs() { + tested.setUpdateMode(true); + tested.setAttributeValue("abc", "123"); + + // Perform test + tested.removeAttributeValue("abc", "123"); + + assertNull(tested.getAttributes().get("abc")); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(0, modificationItems.length); + } + + public void testRemoveAttributeValueInUpdateModeOtherValueExistsInUpdatedAttrs() + throws NamingException { + tested.setUpdateMode(true); + tested.setAttributeValue("abc", "321"); + + // Perform test + tested.removeAttributeValue("abc", "123"); + + assertNull(tested.getAttributes().get("abc")); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + Attribute modificationAttribute = modificationItems[0].getAttribute(); + assertEquals("abc", modificationAttribute.getID()); + assertEquals(1, modificationAttribute.size()); + assertEquals("321", modificationAttribute.get()); + } + + public void testRemoveAttributeValueInUpdateModeOtherAndSameValueExistsInUpdatedAttrs() + throws NamingException { + tested.setUpdateMode(true); + tested.setAttributeValues("abc", new String[] { "321", "123" }); + + // Perform test + tested.removeAttributeValue("abc", "123"); + + assertNull(tested.getAttributes().get("abc")); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + Attribute modificationAttribute = modificationItems[0].getAttribute(); + assertEquals("abc", modificationAttribute.getID()); + assertEquals(1, modificationAttribute.size()); + } + + public void testRemoveAttributeValueInUpdateModeSameValueExistsInOrigAttrs() { + tested.setAttribute(new BasicAttribute("abc", "123")); + tested.setUpdateMode(true); + + // Perform test + tested.removeAttributeValue("abc", "123"); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + Attribute modificationAttribute = modificationItems[0].getAttribute(); + assertEquals("abc", modificationAttribute.getID()); + assertEquals(0, modificationAttribute.size()); + assertEquals(DirContext.REMOVE_ATTRIBUTE, modificationItems[0] + .getModificationOp()); + } + + public void testRemoveAttributeValueInUpdateModeSameAndOtherValueExistsInOrigAttrs() + throws NamingException { + BasicAttribute basicAttribute = new BasicAttribute("abc"); + basicAttribute.add("123"); + basicAttribute.add("321"); + tested.setAttribute(basicAttribute); + tested.setUpdateMode(true); + + // Perform test + tested.removeAttributeValue("abc", "123"); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + Attribute modificationAttribute = modificationItems[0].getAttribute(); + assertEquals("abc", modificationAttribute.getID()); + assertEquals(1, modificationAttribute.size()); + assertEquals("123", modificationAttribute.get()); + assertEquals(DirContext.REMOVE_ATTRIBUTE, modificationItems[0] + .getModificationOp()); + } + + public void testSetStringAttribute() throws Exception { + assertFalse(tested.isUpdateMode()); + tested.setAttributeValue("abc", "123"); + Attributes attrs = tested.getAttributes(); + Attribute attr = attrs.get("abc"); + assertEquals("123", (String) attr.get()); + } + + public void testSetStringAttributeNull() throws Exception { + assertFalse(tested.isUpdateMode()); + tested.setAttributeValue("abc", null); + Attributes attrs = tested.getAttributes(); + Attribute attr = attrs.get("abc"); + assertNull(attr); + } + + public void testAddAttribute() throws Exception { + tested.setUpdateMode(true); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValue("abc", "123"); + Attributes attrs = tested.getAttributes(); + Attribute attr = attrs.get("abc"); + assertNull(attr); + + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(1, mods.length); + assertEquals(DirContext.ADD_ATTRIBUTE, mods[0].getModificationOp()); + attr = mods[0].getAttribute(); + assertEquals("123", (String) attr.get()); + + String[] modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(1, modNames.length); + assertEquals("abc", modNames[0]); + + tested.update(); + mods = tested.getModificationItems(); + assertEquals(0, mods.length); + modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(0, modNames.length); + attrs = tested.getAttributes(); + attr = attrs.get("abc"); + assertEquals("123", (String) attr.get()); + } + + public void testGetDn() throws Exception { + DirContextAdapter tested = new DirContextAdapter(DUMMY_NAME); + Name result = tested.getDn(); + assertEquals(DUMMY_NAME, result); + } + + public void testGetDn_BasePath() { + DirContextAdapter tested = new DirContextAdapter(null, DUMMY_NAME, + BASE_NAME); + Name result = tested.getDn(); + assertEquals(DUMMY_NAME, result); + } + + public void testGetNameInNamespace() { + DirContextAdapter tested = new DirContextAdapter(DUMMY_NAME); + String result = tested.getNameInNamespace(); + assertEquals(DUMMY_NAME.toString(), result); + } + + public void testGetNameInNamespace_BasePath() { + DirContextAdapter tested = new DirContextAdapter(null, + new DistinguishedName("c=SE"), BASE_NAME); + String result = tested.getNameInNamespace(); + assertEquals(DUMMY_NAME.toString(), result); + } + + public void testAddMultiAttributes() throws Exception { + tested.setUpdateMode(true); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValues("abc", new String[] { "123", "456" }); + Attributes attrs = tested.getAttributes(); + Attribute attr = attrs.get("abc"); + assertNull(attr); + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(1, mods.length); + assertEquals(DirContext.ADD_ATTRIBUTE, mods[0].getModificationOp()); + attr = mods[0].getAttribute(); + assertEquals(2, attr.size()); + assertEquals("123", (String) attr.get(0)); + assertEquals("456", (String) attr.get(1)); + + String[] modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(1, modNames.length); + assertEquals("abc", modNames[0]); + + tested.update(); + mods = tested.getModificationItems(); + assertEquals(0, mods.length); + modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(0, modNames.length); + attrs = tested.getAttributes(); + attr = attrs.get("abc"); + assertEquals("123", (String) attr.get(0)); + assertEquals("456", (String) attr.get(1)); + } + + public void testRemoveAttribute() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + fixtureAttrs.put(new BasicAttribute("abc", "123")); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + + tested.setUpdateMode(true); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValue("abc", null); + Attributes attrs = tested.getAttributes(); + Attribute attr = attrs.get("abc"); + assertEquals("123", (String) attr.get()); + + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(1, mods.length); + assertEquals(DirContext.REMOVE_ATTRIBUTE, mods[0].getModificationOp()); + attr = mods[0].getAttribute(); + assertEquals("abc", (String) attr.getID()); + String[] modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(1, modNames.length); + assertEquals("abc", modNames[0]); + + tested.update(); + mods = tested.getModificationItems(); + assertEquals(0, mods.length); + modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(0, modNames.length); + attrs = tested.getAttributes(); + attr = attrs.get("abc"); + assertNull(attr); + } + + public void testRemoveMultiAttribute() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute abc = new BasicAttribute("abc"); + abc.add("123"); + abc.add("456"); + fixtureAttrs.put(abc); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + + tested.setUpdateMode(true); + tested.setAttributeValues("abc", new String[] {}); + + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(1, mods.length); + assertEquals(DirContext.REMOVE_ATTRIBUTE, mods[0].getModificationOp()); + Attribute attr = mods[0].getAttribute(); + assertEquals("abc", (String) attr.getID()); + assertEquals(0, attr.size()); + } + + public void testChangeAttribute() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + fixtureAttrs.put(new BasicAttribute("abc", "123")); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + tested.setAttributeValue("abc", "234"); // change + + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(1, mods.length); + assertEquals(DirContext.REPLACE_ATTRIBUTE, mods[0].getModificationOp()); + Attribute attr = mods[0].getAttribute(); + assertEquals("abc", (String) attr.getID()); + assertEquals("234", (String) attr.get()); + } + + public void testNoChangeAttribute() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + fixtureAttrs.put(new BasicAttribute("abc", "123")); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValue("abc", "123"); // change + + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(0, mods.length); + } + + public void testNoChangeMultiAttribute() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("qwe"); + fixtureAttrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValues("abc", new String[] { "123", "qwe" }); + + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(0, mods.length); + String[] modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(0, modNames.length); + } + + public void testNoChangeMultiAttributeOrderDoesNotMatter() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("qwe"); + fixtureAttrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + tested.setAttributeValues("abc", new String[] { "qwe", "123" }); + + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(0, mods.length); + String[] modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(0, modNames.length); + } + + public void testChangeMultiAttributeOrderDoesMatter() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("qwe"); + fixtureAttrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValues("abc", new String[] { "qwe", "123" }, true); + + // change + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(1, mods.length); + assertEquals(DirContext.REPLACE_ATTRIBUTE, mods[0].getModificationOp()); + Attribute attr = mods[0].getAttribute(); + assertEquals("qwe", attr.get(0)); + assertEquals("123", attr.get(1)); + } + + public void testChangeMultiAttribute_AddValue() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("qwe"); + fixtureAttrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested + .setAttributeValues("abc", + new String[] { "123", "qwe", "klytt" }); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + assertEquals(DirContext.ADD_ATTRIBUTE, modificationItems[0] + .getModificationOp()); + assertEquals("klytt", modificationItems[0].getAttribute().get()); + } + + public void testChangeMultiAttribute_RemoveValue() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("qwe"); + fixtureAttrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValues("abc", new String[] { "123" }); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + assertEquals(DirContext.REMOVE_ATTRIBUTE, modificationItems[0] + .getModificationOp()); + assertEquals("qwe", modificationItems[0].getAttribute().get()); + } + + public void testChangeMultiAttribute_RemoveTwoValues() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("qwe"); + multi.add("rty"); + fixtureAttrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValues("abc", new String[] { "123" }); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + assertEquals(DirContext.REMOVE_ATTRIBUTE, modificationItems[0] + .getModificationOp()); + assertEquals("qwe", modificationItems[0].getAttribute().get(0)); + assertEquals("rty", modificationItems[0].getAttribute().get(1)); + } + + public void testChangeMultiAttribute_RemoveAllValues() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("qwe"); + fixtureAttrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValues("abc", null); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + assertEquals(DirContext.REMOVE_ATTRIBUTE, modificationItems[0] + .getModificationOp()); + } + + public void testChangeMultiAttribute_SameValue() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("qwe"); + fixtureAttrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValues("abc", new String[] { "123", "qwe" }); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(0, modificationItems.length); + } + + public void testChangeMultiAttribute_AddAndRemoveValue() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("qwe"); + multi.add("rty"); + multi.add("uio"); + fixtureAttrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValues("abc", new String[] { "123", "qwe", "klytt", + "kalle" }); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(2, modificationItems.length); + + Attribute modifiedAttribute = modificationItems[0].getAttribute(); + assertEquals(DirContext.REMOVE_ATTRIBUTE, modificationItems[0] + .getModificationOp()); + assertEquals("abc", modifiedAttribute.getID()); + assertEquals(2, modifiedAttribute.size()); + assertEquals("rty", modifiedAttribute.get(0)); + assertEquals("uio", modifiedAttribute.get(1)); + + assertEquals(DirContext.ADD_ATTRIBUTE, modificationItems[1] + .getModificationOp()); + modifiedAttribute = modificationItems[1].getAttribute(); + assertEquals("abc", modifiedAttribute.getID()); + assertEquals(2, modifiedAttribute.size()); + assertEquals("klytt", modifiedAttribute.get(0)); + assertEquals("kalle", modifiedAttribute.get(1)); + } + + public void testAddAttribute_Multivalue() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + Attribute multi = new BasicAttribute("abc"); + multi.add("123"); + multi.add("qwe"); + fixtureAttrs.put(multi); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValues("def", new String[] { "kalle", "klytt" }); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(1, modificationItems.length); + assertEquals("def", modificationItems[0].getAttribute().getID()); + } + + public void testChangeAttributeTwice() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + fixtureAttrs.put(new BasicAttribute("abc", "123")); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValue("abc", "234"); // change + tested.setAttributeValue("abc", "987"); + // change a second time + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(1, mods.length); + assertEquals(DirContext.REPLACE_ATTRIBUTE, mods[0].getModificationOp()); + Attribute attr = mods[0].getAttribute(); + assertEquals("abc", (String) attr.getID()); + assertEquals("987", (String) attr.get()); + + tested.update(); + mods = tested.getModificationItems(); + assertEquals(0, mods.length); + String[] modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(0, modNames.length); + Attributes attrs = tested.getAttributes(); + attr = attrs.get("abc"); + assertEquals("987", (String) attr.get()); + assertEquals("987", tested.getStringAttribute("abc")); + } + + public void testAddReplaceAndChangeAttribute() throws Exception { + final Attributes fixtureAttrs = new BasicAttributes(); + fixtureAttrs.put(new BasicAttribute("abc", "123")); + fixtureAttrs.put(new BasicAttribute("qwe", "42")); + class TestableDirContextAdapter extends DirContextAdapter { + public TestableDirContextAdapter() { + super(fixtureAttrs, null); + setUpdateMode(true); + } + } + tested = new TestableDirContextAdapter(); + assertTrue(tested.isUpdateMode()); + tested.setAttributeValue("abc", "234"); // change + tested.setAttributeValue("qwe", null); // remove + tested.setAttributeValue("zzz", "new"); // new + Attributes attrs = tested.getAttributes(); + Attribute attr = attrs.get("abc"); + assertEquals("123", (String) attr.get()); + assertEquals(2, attrs.size()); + + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(3, mods.length); + String[] modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(3, modNames.length); + + ModificationItem mod = getModificationItem(mods, + DirContext.REPLACE_ATTRIBUTE); + assertNotNull(mod); + attr = mod.getAttribute(); + assertEquals("abc", (String) attr.getID()); + assertEquals("234", (String) attr.get()); + + mod = getModificationItem(mods, DirContext.REMOVE_ATTRIBUTE); + assertNotNull(mod); + attr = mod.getAttribute(); + assertEquals("qwe", (String) attr.getID()); + + mod = getModificationItem(mods, DirContext.ADD_ATTRIBUTE); + assertNotNull(mod); + attr = mod.getAttribute(); + assertEquals("zzz", (String) attr.getID()); + assertEquals("new", (String) attr.get()); + + tested.update(); + mods = tested.getModificationItems(); + assertEquals(0, mods.length); + modNames = tested.getNamesOfModifiedAttributes(); + assertEquals(0, modNames.length); + + attrs = tested.getAttributes(); + assertEquals(2, attrs.size()); + attr = attrs.get("abc"); + assertEquals("234", (String) attr.get()); + assertEquals("new", tested.getStringAttribute("zzz")); + } + + /** + * Test for LDAP-15: DirContextAdapter.setAttribute(). Verifies that setting + * an Attribute should modify updatedAttrs if in update mode. + * + * @throws NamingException + */ + public void testSetAttribute_UpdateMode() throws NamingException { + // Set original attribute value + Attribute attribute = new BasicAttribute("cn", "john doe"); + tested.setAttribute(attribute); + + // Set to update mode + tested.setUpdateMode(true); + + // Perform test - update the attribute + Attribute updatedAttribute = new BasicAttribute("cn", "nisse hult"); + tested.setAttribute(updatedAttribute); + + // Verify result + ModificationItem[] mods = tested.getModificationItems(); + assertEquals(1, mods.length); + assertEquals(DirContext.REPLACE_ATTRIBUTE, mods[0].getModificationOp()); + + Attribute modificationAttribute = mods[0].getAttribute(); + assertEquals("cn", modificationAttribute.getID()); + assertEquals("nisse hult", modificationAttribute.get()); + } + + public void testGetStringAttributes_NullValue() { + String result = tested.getStringAttribute("someAbsentAttribute"); + assertNull(result); + } + + public void testGetStringAttributes_AttributeExists_NullValue() { + tested.setAttribute(new BasicAttribute("someAttribute", null)); + String result = tested.getStringAttribute("someAttribute"); + assertNull(result); + } + + private ModificationItem getModificationItem(ModificationItem[] mods, + int operation) { + for (int i = 0; i < mods.length; i++) { + if (mods[i].getModificationOp() == operation) + return mods[i]; + } + return null; + } + + public void testModifyMultiValueAttributeModificationOrder() + throws NamingException { + BasicAttribute attribute = new BasicAttribute("abc"); + attribute.add("Some Person"); + attribute.add("Some Other Person"); + + tested.setAttribute(attribute); + tested.setUpdateMode(true); + + tested.setAttributeValues("abc", new String[] { "some person", + "Some Other Person" }); + + // Perform test + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(2, modificationItems.length); + ModificationItem modificationItem = modificationItems[0]; + assertEquals(DirContext.REMOVE_ATTRIBUTE, modificationItem + .getModificationOp()); + assertEquals("Some Person", modificationItem.getAttribute().get()); + modificationItem = modificationItems[1]; + assertEquals(DirContext.ADD_ATTRIBUTE, modificationItem + .getModificationOp()); + assertEquals("some person", modificationItem.getAttribute().get()); + } + + /** + * Test for LDAP-13. + */ + public void testModifyAttributeByteArray() { + tested.setAttribute(new BasicAttribute("abc", new byte[] { 1, 2, 3 })); + + tested.setUpdateMode(true); + + // Perform test + tested.setAttributeValue("abc", new byte[] { 1, 2, 3 }); + + ModificationItem[] modificationItems = tested.getModificationItems(); + assertEquals(0, modificationItems.length); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/DistinguishedNameEditorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/DistinguishedNameEditorTest.java new file mode 100644 index 00000000..c912e566 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/DistinguishedNameEditorTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link DistinguishedNameEditor}. + * + * @author Mattias Arthursson + */ +public class DistinguishedNameEditorTest extends TestCase { + + private DistinguishedNameEditor tested; + + protected void setUp() throws Exception { + tested = new DistinguishedNameEditor(); + } + + protected void tearDown() throws Exception { + tested = null; + } + + public void testSetAsText() throws Exception { + String expectedDn = "dc=jayway, dc=se"; + + tested.setAsText(expectedDn); + DistinguishedName result = (DistinguishedName) tested.getValue(); + assertEquals(new DistinguishedName(expectedDn), result); + + try { + result.getNames().add(new LdapRdn("cn", "john doe")); + fail("UnsupportedOperationException expected"); + } + catch (UnsupportedOperationException expected) { + assertTrue(true); + } + } + + public void testSetAsTextNullValue() throws Exception { + tested.setAsText(null); + Object result = tested.getValue(); + assertNull(result); + } + + public void testGetAsText() throws Exception { + String expectedDn = "dc=jayway, dc=se"; + tested.setValue(new DistinguishedName(expectedDn)); + String text = tested.getAsText(); + assertEquals(expectedDn, text); + } + + public void testGetAsTextNullValue() throws Exception { + tested.setValue(null); + String text = tested.getAsText(); + assertNull(text); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/DistinguishedNameTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/DistinguishedNameTest.java new file mode 100644 index 00000000..44c33413 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/DistinguishedNameTest.java @@ -0,0 +1,501 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import java.util.Enumeration; +import java.util.List; + +import javax.naming.CompositeName; +import javax.naming.InvalidNameException; +import javax.naming.Name; + +import org.springframework.ldap.BadLdapGrammarException; +import org.springframework.ldap.core.DistinguishedName; + +import junit.framework.TestCase; + +import com.gargoylesoftware.base.testing.EqualsTester; + +/** + * Unit tests for the {@link DistinguishedName} class. + * + * @author Adam Skogman + * @author Mattias Arthursson + */ +public class DistinguishedNameTest extends TestCase { + + public void testDistinguishedName_CompositeWithSlash() throws Exception { + Name testPath = new CompositeName("cn=foo\\/bar"); + DistinguishedName path = new DistinguishedName(testPath.toString()); + assertEquals("cn=foo/bar", path.toString()); + } + + public void testEmptyPathImmutable() throws Exception { + DistinguishedName emptyPath = DistinguishedName.EMPTY_PATH; + try { + emptyPath.add("cn=John Doe"); + fail("UnsupportedOperationException expected"); + } + catch (UnsupportedOperationException expected) { + assertTrue(true); + } + } + + public void testDistinguishedName() { + + String testPath = "cn=foo\\,bar,OU=FOO\\,bar , OU=foo\\;bar;OU=foo\\;bar" + + " ; ou=foo\\,,ou=foo\\,;ou=foo\\;;ou=foo\\,;ou=bar\\,"; + System.out.println(testPath); + + DistinguishedName path = new DistinguishedName(testPath); + + assertEquals("cn", path.getLdapRdn(8).getComponent().getKey()); + assertEquals("foo,bar", path.getLdapRdn(8).getComponent().getValue()); + assertEquals("ou", path.getLdapRdn(7).getComponent().getKey()); + assertEquals("FOO,bar", path.getLdapRdn(7).getComponent().getValue()); + assertEquals("ou", path.getLdapRdn(6).getComponent().getKey()); + assertEquals("foo;bar", path.getLdapRdn(6).getComponent().getValue()); + assertEquals("ou", path.getLdapRdn(5).getComponent().getKey()); + assertEquals("foo;bar", path.getLdapRdn(5).getComponent().getValue()); + assertEquals("ou", path.getLdapRdn(4).getComponent().getKey()); + assertEquals("foo,", path.getLdapRdn(4).getComponent().getValue()); + assertEquals("ou", path.getLdapRdn(3).getComponent().getKey()); + assertEquals("foo,", path.getLdapRdn(3).getComponent().getValue()); + assertEquals("ou", path.getLdapRdn(2).getComponent().getKey()); + assertEquals("foo;", path.getLdapRdn(2).getComponent().getValue()); + assertEquals("ou", path.getLdapRdn(1).getComponent().getKey()); + assertEquals("foo,", path.getLdapRdn(1).getComponent().getValue()); + assertEquals("ou", path.getLdapRdn(0).getComponent().getKey()); + assertEquals("bar,", path.getLdapRdn(0).getComponent().getValue()); + } + + public void testRemove() throws InvalidNameException { + + String testPath = "cn=john.doe, OU=Users,OU=Some Company,OU=G,OU=I,OU=M"; + DistinguishedName path = new DistinguishedName(testPath); + + path.remove(1); + path.remove(3); + + assertEquals("cn=john.doe, ou=Some Company, ou=G, ou=M", path.toString()); + } + + /** + * Tests parsing and toString. + */ + public void testContains() { + + DistinguishedName migpath = new DistinguishedName("OU=G,OU=I,OU=M"); + DistinguishedName path1 = new DistinguishedName("cn=john.doe, OU=Users,OU=SE,OU=G,OU=I,OU=M"); + DistinguishedName path2 = new DistinguishedName("cn=john.doe, OU=Users,OU=SE,ou=G,OU=i,OU=M, ou=foo"); + DistinguishedName path3 = new DistinguishedName("ou=G,OU=i,OU=M, ou=foo"); + DistinguishedName path4 = new DistinguishedName("ou=G,OU=i,ou=m"); + + DistinguishedName pathE1 = new DistinguishedName("cn=john.doe, OU=Users,OU=SE,ou=G,OU=L,OU=M, ou=foo"); + DistinguishedName pathE2 = new DistinguishedName("cn=john.doe, OU=Users,OU=SE"); + + assertTrue("Contains MIG", path1.contains(migpath)); + assertTrue("Contains MIG", path2.contains(migpath)); + assertTrue("Contains MIG", path3.contains(migpath)); + assertTrue("Contains MIG", path4.contains(migpath)); + + assertFalse("Does not contain MIG", pathE1.contains(migpath)); + assertFalse("Does not contain MIG", pathE2.contains(migpath)); + } + + public void testAppend() { + DistinguishedName path1 = new DistinguishedName("ou=foo, OU=bar"); + DistinguishedName path2 = new DistinguishedName("OU=baz"); + + path1.append(path2); + + assertEquals("Append failed", "ou=baz, ou=foo, ou=bar", path1.toString()); + } + + public void testPrepend() { + DistinguishedName path1 = new DistinguishedName("ou=foo, OU=bar"); + DistinguishedName path2 = new DistinguishedName("cn=fie, OU=baz"); + + path1.prepend(path2); + + assertEquals("Append failed", "ou=foo, ou=bar, cn=fie, ou=baz", path1.toString()); + } + + public void testEquals() throws Exception { + + // original object + final Object originalObject = new DistinguishedName("cn=john.doe, OU=Users,OU=Some company,C=SE"); + + // another object that has the same values as the original + final Object identicalObject = new DistinguishedName("cn=john.doe, OU=Users,OU=Some company,C=SE"); + + // another object with different values + final Object differentObject = new DistinguishedName("cn=john.doe, OU=Users,OU=Some other company,C=SE"); + + // a subclass with the same values as the original + final Object subclassObject = new DistinguishedName("cn=john.doe, OU=Users,OU=Some company,C=SE") { + private static final long serialVersionUID = 1L; + }; + + new EqualsTester(originalObject, identicalObject, differentObject, subclassObject); + } + + public void testClone() { + + DistinguishedName path1 = new DistinguishedName("cn=john.doe, OU=Users,OU=Some company,C=SE"); + + DistinguishedName path2 = (DistinguishedName) path1.clone(); + + assertEquals("Should be equal", path1, path2); + + path2.removeFirst(); + assertFalse("Should not be equal", path1.equals(path2)); + + } + + public void testEndsWith_true() { + DistinguishedName path1 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + DistinguishedName ending1 = new DistinguishedName("uid=mtah.test"); + + DistinguishedName path2 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + DistinguishedName ending2 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU"); + + assertTrue(path1.endsWith(ending1)); + assertTrue(path2.endsWith(ending2)); + } + + public void testEndsWith_false() { + DistinguishedName path1 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + DistinguishedName ending1 = new DistinguishedName("ou=people"); + + DistinguishedName path2 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + DistinguishedName ending2 = new DistinguishedName("ou=EU, o=example.com"); + + assertFalse(path1.endsWith(ending1)); + assertFalse(path2.endsWith(ending2)); + } + + public void testGetAll() throws Exception { + DistinguishedName path = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + + Enumeration elements = path.getAll(); + + String element = (String) elements.nextElement(); + assertEquals("o=example.com", element); + + element = (String) elements.nextElement(); + assertEquals("ou=EU", element); + + element = (String) elements.nextElement(); + assertEquals("ou=people", element); + + element = (String) elements.nextElement(); + assertEquals("uid=mtah.test", element); + } + + public void testGet() throws Exception { + DistinguishedName path = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + + String string = path.get(1); + + assertEquals("ou=EU", string); + } + + public void testSize() { + DistinguishedName path1 = new DistinguishedName(); + assertEquals(0, path1.size()); + + DistinguishedName path2 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + assertEquals(4, path2.size()); + } + + public void testGetPrefix() { + DistinguishedName path = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + + Name prefix = path.getPrefix(0); + assertEquals(0, prefix.size()); + + prefix = path.getPrefix(1); + assertEquals(1, prefix.size()); + assertEquals("o=example.com", prefix.get(0)); + + prefix = path.getPrefix(2); + assertEquals(2, prefix.size()); + assertEquals("o=example.com", prefix.get(0)); + assertEquals("ou=EU", prefix.get(1)); + } + + public void testGetSuffix() { + DistinguishedName path = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + + Name suffix = path.getSuffix(0); + assertEquals(4, suffix.size()); + + suffix = path.getSuffix(2); + assertEquals(2, suffix.size()); + assertEquals("ou=people", suffix.get(0)); + + suffix = path.getSuffix(4); + assertEquals(0, suffix.size()); + + try { + path.getSuffix(5); + fail("ArrayIndexOutOfBoundsException expected"); + } + catch (ArrayIndexOutOfBoundsException expected) { + assertTrue(true); + } + } + + public void testStartsWith_true() { + DistinguishedName path1 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + DistinguishedName start1 = new DistinguishedName("o=example.com"); + + DistinguishedName path2 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + DistinguishedName start2 = new DistinguishedName("ou=people, ou=EU, o=example.com"); + + assertTrue(path1.startsWith(start1)); + assertTrue(path2.startsWith(start2)); + } + + public void testStartsWith_false() { + DistinguishedName path1 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + DistinguishedName start1 = new DistinguishedName("ou=people"); + + DistinguishedName path2 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + DistinguishedName start2 = new DistinguishedName("uid=mtah.test, ou=EU, ou=people"); + + assertFalse(path1.startsWith(start1)); + assertFalse(path2.startsWith(start2)); + } + + public void testStartsWith_Longer() { + DistinguishedName path1 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + + DistinguishedName path2 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com, o=a.com"); + + assertFalse(path1.startsWith(path2)); + } + + public void testStartsWith_EmptyPath() { + DistinguishedName path1 = new DistinguishedName("uid=mtah.test, ou=people, ou=EU, o=example.com"); + + DistinguishedName path2 = new DistinguishedName(); + + assertFalse(path1.startsWith(path2)); + } + + public void testIsEmpty_True() { + DistinguishedName path = new DistinguishedName(); + assertTrue(path.isEmpty()); + } + + public void testIsEmpty_False() { + DistinguishedName path = new DistinguishedName("o=example.com"); + assertFalse(path.isEmpty()); + } + + public void testAddAll() throws Exception { + DistinguishedName path1 = new DistinguishedName("ou=foo, OU=bar"); + DistinguishedName path2 = new DistinguishedName("OU=baz"); + + path1.addAll(path2); + + assertEquals("AddAll failed", "ou=baz, ou=foo, ou=bar", path1.toString()); + } + + public void testAddAll_Index() throws InvalidNameException { + DistinguishedName path1 = new DistinguishedName("ou=foo, OU=bar"); + DistinguishedName path2 = new DistinguishedName("OU=baz"); + + path1.addAll(1, path2); + + assertEquals("AddAll failed", "ou=foo, ou=baz, ou=bar", path1.toString()); + } + + public void testAdd() throws InvalidNameException { + DistinguishedName path1 = new DistinguishedName("ou=foo, ou=bar"); + path1.add("ou=baz"); + + assertEquals("Add failed", "ou=baz, ou=foo, ou=bar", path1.toString()); + } + + public void testAdd_Index() throws InvalidNameException { + DistinguishedName path1 = new DistinguishedName("ou=foo, ou=bar"); + path1.add(1, "ou=baz"); + + assertEquals("Add failed", "ou=foo, ou=baz, ou=bar", path1.toString()); + } + + public void testToUrl() { + DistinguishedName path = new DistinguishedName("dc=jayway, dc=se"); + String url = path.toUrl(); + + assertEquals("dc=jayway,dc=se", url); + } + + public void testMultiValueRdn() throws Exception { + DistinguishedName path = new DistinguishedName("firstName=Rod+lastName=Johnson,ou=UK,dc=interface21,dc=com"); + assertEquals(4, path.size()); + assertEquals("firstname=Rod+lastname=Johnson", path.get(3)); + } + + public void testCompareTo_Equals() throws Exception { + DistinguishedName name1 = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + DistinguishedName name2 = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + + int result = name1.compareTo(name2); + assertEquals(0, result); + } + + public void testCompareTo_Less() throws Exception { + DistinguishedName name1 = new DistinguishedName("cn=john doe, ou=Some company, c=DK"); + DistinguishedName name2 = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + + int result = name1.compareTo(name2); + assertTrue(result < 0); + } + + public void testCompareTo_Less_MoreSignificant() throws Exception { + DistinguishedName name1 = new DistinguishedName("an=john doe, ou=Some company, c=DK"); + DistinguishedName name2 = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + + int result = name1.compareTo(name2); + assertTrue(result < 0); + } + + public void testCompareTo_Greater() throws Exception { + DistinguishedName name1 = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + DistinguishedName name2 = new DistinguishedName("cn=john doe, ou=Some company, c=DK"); + + int result = name1.compareTo(name2); + assertTrue(result > 0); + } + + public void testCompareTo_Longer() throws Exception { + DistinguishedName name1 = new DistinguishedName("leaf=someleaf, cn=john doe, ou=Some company, c=SE"); + DistinguishedName name2 = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + + int result = name1.compareTo(name2); + assertTrue(result > 0); + } + + public void testCompareTo_Shorter() throws Exception { + DistinguishedName name1 = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + DistinguishedName name2 = new DistinguishedName("leaf=someleaf, cn=john doe, ou=Some company, c=SE"); + + int result = name1.compareTo(name2); + assertTrue(result < 0); + } + + public void testGetLdapRdnForKey() throws Exception { + DistinguishedName dn = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + LdapRdn ldapRdn = dn.getLdapRdn("ou"); + assertEquals(new LdapRdn("ou=Some company"), ldapRdn); + } + + public void testGetLdapRdnForKeyNoMatchingKeyThrowsException() throws Exception { + DistinguishedName dn = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + try { + dn.getLdapRdn("nosuchkey"); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testGetValue() throws Exception { + DistinguishedName dn = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + String value = dn.getValue("ou"); + assertEquals("Some company", value); + } + + public void testGetValueNoMatchingKeyThrowsException() throws Exception { + DistinguishedName dn = new DistinguishedName("cn=john doe, ou=Some company, c=SE"); + try { + dn.getValue("nosuchkey"); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void test_longDN() throws InvalidNameException { + DistinguishedName name = new DistinguishedName(""); + assertNotNull(name); + } + + /** + * Test case to verify correct parsing for issue on forums. + */ + public void testParseAtSign() { + DistinguishedName name = new DistinguishedName("cn=testname@example.com"); + assertNotNull(name); + } + + /** + * Test case to verify correct parsing for issue on forums. + */ + public void testParseAtSign2() { + DistinguishedName name = new DistinguishedName("cn=te\\+stname@example.com"); + assertNotNull(name); + } + + /** + * Test case to verify correct parsing for issue on forums. + */ + public void testParseInvalidPlus() { + try { + new DistinguishedName("cn=te+stname@example.com"); + fail("BadLdapGrammarException expected"); + } + catch (BadLdapGrammarException expected) { + assertTrue(true); + } + } + + /** + * Test case to verify correct parsing for issue on forums. + */ + public void testParseValidQuotation() { + DistinguishedName name = new DistinguishedName("cn=jo\"hn doe"); + assertNotNull(name); + } + + public void testAppendChained() { + DistinguishedName tested = new DistinguishedName("dc=mycompany,dc=com"); + tested.append("ou", "company1").append("cn", "john doe"); + + assertEquals("cn=john doe, ou=company1, dc=mycompany, dc=com", tested.toString()); + } + + public void testUnmodifiableDistinguishedName() throws Exception { + DistinguishedName name = new DistinguishedName("cn=john doe"); + + DistinguishedName result = name.immutableDistinguishedName(); + List names = result.getNames(); + try { + names.add(new LdapRdnComponent("somekey", "somevalue")); + fail("UnsupportedOperationException expected"); + } + catch (UnsupportedOperationException expected) { + assertTrue(true); + } + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapEncoderTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapEncoderTest.java new file mode 100644 index 00000000..09a4b942 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapEncoderTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import org.springframework.ldap.BadLdapGrammarException; +import org.springframework.ldap.core.LdapEncoder; + +import junit.framework.TestCase; + +/** + * Unit test for the LdapEncode class. + * + * @author Adam Skogman + */ +public class LdapEncoderTest extends TestCase { + + /** + * Constructor for LdapEncoderTest. + * + * @param name + */ + public LdapEncoderTest(String name) { + super(name); + } + + public void testFilterEncode() { + String correct = "\\2aa\\2ab\\28c\\29d\\2a\\5c"; + assertEquals(correct, LdapEncoder.filterEncode("*a*b(c)d*\\")); + + } + + public void testNameEncode() { + + String res = LdapEncoder.nameEncode("# foo ,+\"\\<>; "); + + assertEquals("\\# foo \\,\\+\\\"\\\\\\<\\>\\;\\ ", res); + } + + public void testNameDecode() { + + String res = (String) LdapEncoder + .nameDecode("\\# foo \\,\\+\\\"\\\\\\<\\>\\;\\ "); + + assertEquals("# foo ,+\"\\<>; ", res); + } + + public void testNameDecode_slashlast() { + + try { + LdapEncoder.nameDecode("\\"); + fail("Should throw BadLdapGrammarException"); + } catch (BadLdapGrammarException e) { + assertTrue(true); + } + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapRdnComponentTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapRdnComponentTest.java new file mode 100644 index 00000000..9643b109 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapRdnComponentTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import org.springframework.ldap.core.LdapRdnComponent; + +import junit.framework.TestCase; + +/** + * Tests for LdapRdnComponent. + * + * @author Mattias Arthursson + */ +public class LdapRdnComponentTest extends TestCase { + + protected void setUp() throws Exception { + super.setUp(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testCompareTo_Less() { + LdapRdnComponent component1 = new LdapRdnComponent("cn", "john doe"); + LdapRdnComponent component2 = new LdapRdnComponent("sn", "doe"); + int result = component1.compareTo(component2); + assertTrue(result < 0); + } + + public void testCompareTo_Greater() { + LdapRdnComponent component1 = new LdapRdnComponent("sn", "doe"); + LdapRdnComponent component2 = new LdapRdnComponent("cn", "john doe"); + int result = component1.compareTo(component2); + assertTrue(result > 0); + } + + public void testCompareTo_Equal() { + LdapRdnComponent component1 = new LdapRdnComponent("cn", "john doe"); + LdapRdnComponent component2 = new LdapRdnComponent("cn", "john doe"); + int result = component1.compareTo(component2); + assertEquals(0, result); + } +} \ No newline at end of file diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapRdnTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapRdnTest.java new file mode 100644 index 00000000..b228dc40 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapRdnTest.java @@ -0,0 +1,240 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import junit.framework.TestCase; + +import org.springframework.ldap.BadLdapGrammarException; + +import com.gargoylesoftware.base.testing.EqualsTester; + +/** + * Unit test for the LdapRdn class. + * + * @author Adam Skogman + */ +public class LdapRdnTest extends TestCase { + + public void testLdapRdn_parse_simple() { + + LdapRdn rdn = new LdapRdn("foo=bar"); + + assertEquals("foo", rdn.getComponent().getKey()); + assertEquals("bar", rdn.getComponent().getValue()); + assertEquals("foo=bar", rdn.getComponent().getLdapEncoded()); + assertEquals("foo", rdn.getKey()); + assertEquals("bar", rdn.getValue()); + } + + public void testLdapRdn_parse_spaces() { + + LdapRdn rdn = new LdapRdn(" foo = bar "); + + assertEquals("foo", rdn.getComponent().getKey()); + assertEquals("bar", rdn.getComponent().getValue()); + assertEquals("foo=bar", rdn.getComponent().getLdapEncoded()); + } + + public void testLdapRdn_parse_escape() { + + LdapRdn rdn = new LdapRdn("foo=bar\\=fum"); + + assertEquals("foo", rdn.getComponent().getKey()); + assertEquals("bar=fum", rdn.getComponent().getValue()); + assertEquals("foo=bar\\=fum", rdn.getComponent().getLdapEncoded()); + } + + public void testLdapRdn_parse_hexEscape() { + + LdapRdn rdn = new LdapRdn("foo=bar\\0dfum"); + + assertEquals("foo", rdn.getComponent().getKey()); + assertEquals("bar\rfum", rdn.getComponent().getValue()); + assertEquals("foo=bar\\0Dfum", rdn.getComponent().getLdapEncoded()); + } + + public void testLdapRdn_parse_trailingBackslash() { + + try { + new LdapRdn("foo=bar\\"); + fail("Should throw BadLdapGrammarException"); + } catch (BadLdapGrammarException e) { + assertTrue(true); + } + } + + public void testLdapRdn_parse_spaces_escape() { + + LdapRdn rdn = new LdapRdn(" foo = \\ bar\\20 \\ "); + + assertEquals("foo", rdn.getComponent().getKey()); + assertEquals(" bar ", rdn.getComponent().getValue()); + assertEquals("foo=\\ bar \\ ", rdn.getComponent().getLdapEncoded()); + } + + public void testLdapRdn_parse_tooMuchTrim() { + try { + new LdapRdn("foo=bar\\"); + fail("Should throw BadLdapGrammarException"); + } catch (BadLdapGrammarException e) { + assertTrue(true); + } + } + + public void testLdapRdn_parse_slash() { + LdapRdn rdn = new LdapRdn("ou=Clerical / Secretarial Staff"); + + assertEquals("ou", rdn.getComponent().getKey()); + assertEquals("Clerical / Secretarial Staff", rdn.getComponent() + .getValue()); + assertEquals("ou=Clerical / Secretarial Staff", rdn.getComponent() + .getLdapEncoded()); + } + + public void testLdapRdn_parse_quoteInKey() { + try { + new LdapRdn("\"umanroleid=2583"); + fail("Should throw BadLdapGrammarException"); + } catch (BadLdapGrammarException e) { + assertTrue(true); + } + } + + public void testLdapRdn_KeyValue_simple() { + LdapRdn rdn = new LdapRdn("foo", "bar"); + + assertEquals("foo", rdn.getComponent().getKey()); + assertEquals("bar", rdn.getComponent().getValue()); + assertEquals("foo=bar", rdn.getComponent().getLdapEncoded()); + } + + public void testLdapRdn_KeyValue_valueNeedsEscape() { + LdapRdn rdn = new LdapRdn("foo", "bar\\"); + + assertEquals("foo", rdn.getComponent().getKey()); + assertEquals("bar\\", rdn.getComponent().getValue()); + assertEquals("foo=bar\\\\", rdn.getComponent().getLdapEncoded()); + } + + public void testEncodeUrl() { + LdapRdn rdn = new LdapRdn("o = example.com "); + assertEquals("o=example.com", rdn.encodeUrl()); + } + + public void testEncodeUrl_SpacesInValue() { + LdapRdn rdn = new LdapRdn("o = my organization "); + assertEquals("o=my%20organization", rdn.encodeUrl()); + } + + public void testLdapRdn_Parse_MultipleComponents() { + LdapRdn rdn = new LdapRdn("cn=John Doe+sn=Doe"); + assertEquals("cn=John Doe", rdn.getComponent(0).encodeLdap()); + assertEquals("sn=Doe", rdn.getComponent(1).encodeLdap()); + assertEquals("cn=John Doe+sn=Doe", rdn.getLdapEncoded()); + assertEquals("cn", rdn.getKey()); + assertEquals("John Doe", rdn.getValue()); + assertEquals("John Doe", rdn.getValue("cn")); + assertEquals("Doe", rdn.getValue("sn")); + } + + public void testGetValueNoKeyWithCorrectValue() { + LdapRdn tested = new LdapRdn("cn=john doe"); + try { + tested.getValue("sn"); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testGetValueNoComponents() { + LdapRdn tested = new LdapRdn(); + try { + tested.getValue("sn"); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testEquals() throws Exception { + // original object + final Object originalObject = new LdapRdn("cn", "john.doe"); + + // another object that has the same values as the original + final Object identicalObject = new LdapRdn("cn", "john.doe"); + + // another object with different values + final Object differentObject = new LdapRdn("cn", "john.svensson"); + + // a subclass with the same values as the original + final Object subclassObject = new LdapRdn("cn", "john.doe") { + private static final long serialVersionUID = 1L; + }; + + new EqualsTester(originalObject, identicalObject, differentObject, + subclassObject); + } + + public void testCompareTo_Equals() throws Exception { + LdapRdn rdn1 = new LdapRdn("cn=john doe"); + LdapRdn rdn2 = new LdapRdn("cn=john doe"); + + int result = rdn1.compareTo(rdn2); + assertEquals(0, result); + } + + public void testCompareTo_EqualsComplex() throws Exception { + LdapRdn rdn1 = new LdapRdn("cn=john doe+sn=doe"); + LdapRdn rdn2 = new LdapRdn("cn=john doe+sn=doe"); + + int result = rdn1.compareTo(rdn2); + assertEquals(0, result); + } + + public void testCompareTo_Less() { + LdapRdn rdn1 = new LdapRdn("cn=john doe+sn=doe"); + LdapRdn rdn2 = new LdapRdn("cn=john doe+tn=doe"); + + int result = rdn1.compareTo(rdn2); + assertTrue(result < 0); + } + + public void testCompareTo_Greater() { + LdapRdn rdn1 = new LdapRdn("cn=john doe+sn=doe"); + LdapRdn rdn2 = new LdapRdn("cn=john doe+an=doe"); + + int result = rdn1.compareTo(rdn2); + assertTrue(result > 0); + } + + public void testCompareTo_Shorter() { + LdapRdn rdn1 = new LdapRdn("cn=john doe+sn=doe"); + LdapRdn rdn2 = new LdapRdn("cn=john doe+sn=doe+description=tjo"); + + int result = rdn1.compareTo(rdn2); + assertTrue(result < 0); + } + + public void testCompareTo_Longer() { + LdapRdn rdn1 = new LdapRdn("cn=john doe+sn=doe+description=tjo"); + LdapRdn rdn2 = new LdapRdn("cn=john doe+sn=doe"); + + int result = rdn1.compareTo(rdn2); + assertTrue(result > 0); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateListTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateListTest.java new file mode 100644 index 00000000..bdd6959f --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateListTest.java @@ -0,0 +1,419 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import java.util.List; + +import javax.naming.Binding; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.LimitExceededException; +import org.springframework.ldap.PartialResultException; + +/** + * Unit tests for the list operations in {@link LdapTemplate}. + * + * @author Ulrik Sandberg + */ +public class LdapTemplateListTest extends TestCase { + + private static final String NAME = "o=example.com"; + + private static final String CLASS = "com.example.SomeClass"; + + private MockControl contextSourceControl; + + private ContextSource contextSourceMock; + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + private MockControl namingEnumerationControl; + + private NamingEnumeration namingEnumerationMock; + + private MockControl nameControl; + + private Name nameMock; + + private MockControl handlerControl; + + private NameClassPairCallbackHandler handlerMock; + + private MockControl contextMapperControl; + + private ContextMapper contextMapperMock; + + private LdapTemplate tested; + + protected void setUp() throws Exception { + super.setUp(); + + // Setup ContextSource mock + contextSourceControl = MockControl.createControl(ContextSource.class); + contextSourceMock = (ContextSource) contextSourceControl.getMock(); + + // Setup LdapContext mock + dirContextControl = MockControl.createControl(LdapContext.class); + dirContextMock = (LdapContext) dirContextControl.getMock(); + + // Setup NamingEnumeration mock + namingEnumerationControl = MockControl + .createControl(NamingEnumeration.class); + namingEnumerationMock = (NamingEnumeration) namingEnumerationControl + .getMock(); + + // Setup Name mock + nameControl = MockControl.createControl(Name.class); + nameMock = (Name) nameControl.getMock(); + + // Setup Handler mock + handlerControl = MockControl + .createControl(NameClassPairCallbackHandler.class); + handlerMock = (NameClassPairCallbackHandler) handlerControl.getMock(); + + contextMapperControl = MockControl.createControl(ContextMapper.class); + contextMapperMock = (ContextMapper) contextMapperControl.getMock(); + + tested = new LdapTemplate(contextSourceMock); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + contextSourceControl = null; + contextSourceMock = null; + + dirContextControl = null; + dirContextMock = null; + + namingEnumerationControl = null; + namingEnumerationMock = null; + + nameControl = null; + nameMock = null; + + handlerControl = null; + handlerMock = null; + + contextMapperControl = null; + contextMapperMock = null; + } + + protected void replay() { + contextSourceControl.replay(); + dirContextControl.replay(); + namingEnumerationControl.replay(); + nameControl.replay(); + handlerControl.replay(); + contextMapperControl.replay(); + } + + protected void verify() { + contextSourceControl.verify(); + dirContextControl.verify(); + namingEnumerationControl.verify(); + nameControl.verify(); + handlerControl.verify(); + contextMapperControl.verify(); + } + + private void expectGetReadOnlyContext() { + contextSourceControl.expectAndReturn(contextSourceMock + .getReadOnlyContext(), dirContextMock); + } + + private void setupStringListAndNamingEnumeration(NameClassPair listResult) + throws NamingException { + dirContextControl.expectAndReturn(dirContextMock.list(NAME), + namingEnumerationMock); + + setupNamingEnumeration(listResult); + } + + private void setupListAndNamingEnumeration(NameClassPair listResult) + throws NamingException { + dirContextControl.expectAndReturn(dirContextMock.list(nameMock), + namingEnumerationMock); + + setupNamingEnumeration(listResult); + } + + private void setupStringListBindingsAndNamingEnumeration( + NameClassPair listResult) throws NamingException { + dirContextControl.expectAndReturn(dirContextMock.listBindings(NAME), + namingEnumerationMock); + + setupNamingEnumeration(listResult); + } + + private void setupListBindingsAndNamingEnumeration(NameClassPair listResult) + throws NamingException { + dirContextControl.expectAndReturn( + dirContextMock.listBindings(nameMock), namingEnumerationMock); + + setupNamingEnumeration(listResult); + } + + private void setupNamingEnumeration(NameClassPair listResult) + throws NamingException { + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), true); + namingEnumerationControl.expectAndReturn(namingEnumerationMock.next(), + listResult); + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), false); + namingEnumerationMock.close(); + } + + public void testList_Name() throws NamingException { + expectGetReadOnlyContext(); + + NameClassPair listResult = new NameClassPair(NAME, CLASS); + + setupListAndNamingEnumeration(listResult); + + dirContextMock.close(); + + replay(); + + List list = tested.list(nameMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(NAME, list.get(0)); + } + + public void testList_String() throws NamingException { + expectGetReadOnlyContext(); + + NameClassPair listResult = new NameClassPair(NAME, CLASS); + + setupStringListAndNamingEnumeration(listResult); + + dirContextMock.close(); + + replay(); + + List list = tested.list(NAME); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(NAME, list.get(0)); + } + + public void testList_Name_CallbackHandler() throws NamingException { + expectGetReadOnlyContext(); + + NameClassPair listResult = new NameClassPair(NAME, CLASS); + + setupListAndNamingEnumeration(listResult); + + handlerMock.handleNameClassPair(listResult); + + dirContextMock.close(); + + replay(); + + tested.list(nameMock, handlerMock); + + verify(); + } + + public void testList_String_CallbackHandler() throws NamingException { + expectGetReadOnlyContext(); + + NameClassPair listResult = new NameClassPair(NAME, CLASS); + + setupStringListAndNamingEnumeration(listResult); + + handlerMock.handleNameClassPair(listResult); + + dirContextMock.close(); + + replay(); + + tested.list("o=example.com", handlerMock); + + verify(); + } + + public void testList_PartialResultException() throws NamingException { + expectGetReadOnlyContext(); + + javax.naming.PartialResultException pre = new javax.naming.PartialResultException(); + dirContextControl.expectAndThrow(dirContextMock.list(NAME), pre); + + dirContextMock.close(); + + replay(); + + try { + tested.list(NAME); + fail("PartialResultException expected"); + } catch (PartialResultException expected) { + assertTrue(true); + } + + verify(); + } + + public void testList_PartialResultException_Ignore() throws NamingException { + expectGetReadOnlyContext(); + + javax.naming.PartialResultException pre = new javax.naming.PartialResultException(); + dirContextControl.expectAndThrow(dirContextMock.list(NAME), pre); + + dirContextMock.close(); + + tested.setIgnorePartialResultException(true); + + replay(); + + List list = tested.list(NAME); + + verify(); + + assertNotNull(list); + assertEquals(0, list.size()); + } + + public void testList_NamingException() throws NamingException { + expectGetReadOnlyContext(); + + javax.naming.LimitExceededException ne = new javax.naming.LimitExceededException(); + dirContextControl.expectAndThrow(dirContextMock.list(NAME), ne); + + dirContextMock.close(); + + replay(); + + try { + tested.list(NAME); + fail("LimitExceededException expected"); + } catch (LimitExceededException expected) { + assertTrue(true); + } + + verify(); + } + + // Tests for listBindings + + public void testListBindings_String() throws NamingException { + expectGetReadOnlyContext(); + + Binding listResult = new Binding(NAME, CLASS, null); + + setupStringListBindingsAndNamingEnumeration(listResult); + + dirContextMock.close(); + + replay(); + + List list = tested.listBindings(NAME); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(NAME, list.get(0)); + } + + public void testListBindings_Name() throws NamingException { + expectGetReadOnlyContext(); + + Binding listResult = new Binding(NAME, CLASS, null); + + setupListBindingsAndNamingEnumeration(listResult); + + dirContextMock.close(); + + replay(); + + List list = tested.listBindings(nameMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(NAME, list.get(0)); + } + + public void testListBindings_ContextMapper() throws NamingException { + expectGetReadOnlyContext(); + + Object expectedObject = new Object(); + Binding listResult = new Binding("", expectedObject); + + setupStringListBindingsAndNamingEnumeration(listResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.listBindings(NAME, contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testListBindings_Name_ContextMapper() throws NamingException { + expectGetReadOnlyContext(); + + Object expectedObject = new Object(); + Binding listResult = new Binding("", expectedObject); + + setupListBindingsAndNamingEnumeration(listResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.listBindings(nameMock, contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateLookupTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateLookupTest.java new file mode 100644 index 00000000..0715eeb8 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateLookupTest.java @@ -0,0 +1,426 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.Name; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.NameNotFoundException; + +public class LdapTemplateLookupTest extends TestCase { + + private static final String DEFAULT_BASE_STRING = "o=example.com"; + + private MockControl contextSourceControl; + + private ContextSource contextSourceMock; + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + private MockControl attributesMapperControl; + + private AttributesMapper attributesMapperMock; + + private MockControl nameControl; + + private Name nameMock; + + private MockControl contextMapperControl; + + private ContextMapper contextMapperMock; + + private LdapTemplate tested; + + protected void setUp() throws Exception { + super.setUp(); + + // Setup ContextSource mock + contextSourceControl = MockControl.createControl(ContextSource.class); + contextSourceMock = (ContextSource) contextSourceControl.getMock(); + + // Setup LdapContext mock + dirContextControl = MockControl.createControl(LdapContext.class); + dirContextMock = (LdapContext) dirContextControl.getMock(); + + // Setup Name mock + nameControl = MockControl.createControl(Name.class); + nameMock = (Name) nameControl.getMock(); + + contextMapperControl = MockControl.createControl(ContextMapper.class); + contextMapperMock = (ContextMapper) contextMapperControl.getMock(); + + attributesMapperControl = MockControl + .createControl(AttributesMapper.class); + attributesMapperMock = (AttributesMapper) attributesMapperControl + .getMock(); + + tested = new LdapTemplate(contextSourceMock); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + contextSourceControl = null; + contextSourceMock = null; + + dirContextControl = null; + dirContextMock = null; + + nameControl = null; + nameMock = null; + + contextMapperControl = null; + contextMapperMock = null; + + attributesMapperControl = null; + attributesMapperMock = null; + } + + protected void replay() { + contextSourceControl.replay(); + dirContextControl.replay(); + nameControl.replay(); + contextMapperControl.replay(); + attributesMapperControl.replay(); + } + + protected void verify() { + contextSourceControl.verify(); + dirContextControl.verify(); + nameControl.verify(); + contextMapperControl.verify(); + attributesMapperControl.verify(); + } + + private void expectGetReadOnlyContext() { + contextSourceControl.expectAndReturn(contextSourceMock + .getReadOnlyContext(), dirContextMock); + } + + // Tests for lookup(name) + + public void testLookup() throws Exception { + expectGetReadOnlyContext(); + + Object expected = new Object(); + dirContextControl.expectAndReturn(dirContextMock.lookup(nameMock), + expected); + + dirContextMock.close(); + + replay(); + + Object actual = tested.lookup(nameMock); + + verify(); + + assertSame(expected, actual); + } + + public void testLookup_String() throws Exception { + expectGetReadOnlyContext(); + + Object expected = new Object(); + dirContextControl.expectAndReturn(dirContextMock + .lookup(DEFAULT_BASE_STRING), expected); + + dirContextMock.close(); + + replay(); + + Object actual = tested.lookup(DEFAULT_BASE_STRING); + + verify(); + + assertSame(expected, actual); + } + + public void testLookup_NamingException() throws Exception { + expectGetReadOnlyContext(); + + javax.naming.NameNotFoundException ne = new javax.naming.NameNotFoundException(); + dirContextControl.expectAndThrow(dirContextMock.lookup(nameMock), ne); + + dirContextMock.close(); + + replay(); + + try { + tested.lookup(nameMock); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + verify(); + } + + // Tests for lookup(name, AttributesMapper) + + public void testLookup_AttributesMapper() throws Exception { + expectGetReadOnlyContext(); + + BasicAttributes expectedAttributes = new BasicAttributes(); + dirContextControl.expectAndReturn(dirContextMock + .getAttributes(nameMock), expectedAttributes); + dirContextMock.close(); + + Object expected = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expected); + + replay(); + + Object actual = tested.lookup(nameMock, attributesMapperMock); + + verify(); + + assertSame(expected, actual); + } + + public void testLookup_String_AttributesMapper() throws Exception { + expectGetReadOnlyContext(); + + BasicAttributes expectedAttributes = new BasicAttributes(); + dirContextControl.expectAndReturn(dirContextMock + .getAttributes(DEFAULT_BASE_STRING), expectedAttributes); + dirContextMock.close(); + + Object expected = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expected); + + replay(); + + Object actual = tested + .lookup(DEFAULT_BASE_STRING, attributesMapperMock); + + verify(); + + assertSame(expected, actual); + } + + public void testLookup_AttributesMapper_NamingException() throws Exception { + expectGetReadOnlyContext(); + + javax.naming.NameNotFoundException ne = new javax.naming.NameNotFoundException(); + dirContextControl.expectAndThrow( + dirContextMock.getAttributes(nameMock), ne); + dirContextMock.close(); + + replay(); + + try { + tested.lookup(nameMock, attributesMapperMock); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + verify(); + } + + // Tests for lookup(name, ContextMapper) + + public void testLookup_ContextMapper() throws Exception { + expectGetReadOnlyContext(); + + Object transformed = new Object(); + Object expected = new Object(); + dirContextControl.expectAndReturn(dirContextMock.lookup(nameMock), + expected); + + dirContextMock.close(); + + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expected), transformed); + + replay(); + + Object actual = tested.lookup(nameMock, contextMapperMock); + + verify(); + + assertSame(transformed, actual); + } + + public void testLookup_String_ContextMapper() throws Exception { + expectGetReadOnlyContext(); + + Object transformed = new Object(); + Object expected = new Object(); + dirContextControl.expectAndReturn(dirContextMock + .lookup(DEFAULT_BASE_STRING), expected); + + dirContextMock.close(); + + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expected), transformed); + + replay(); + + Object actual = tested.lookup(DEFAULT_BASE_STRING, contextMapperMock); + + verify(); + + assertSame(transformed, actual); + } + + public void testLookup_ContextMapper_NamingException() throws Exception { + expectGetReadOnlyContext(); + + javax.naming.NameNotFoundException ne = new javax.naming.NameNotFoundException(); + dirContextControl.expectAndThrow(dirContextMock.lookup(nameMock), ne); + + dirContextMock.close(); + + replay(); + + try { + tested.lookup(nameMock, contextMapperMock); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + verify(); + } + + // Tests for lookup(name, attributes, AttributesMapper) + + public void testLookup_ReturnAttributes_AttributesMapper() throws Exception { + expectGetReadOnlyContext(); + + String[] attributeNames = new String[] { "cn" }; + + BasicAttributes expectedAttributes = new BasicAttributes(); + expectedAttributes.put("cn", "Some Name"); + + dirContextControl.expectAndReturn(dirContextMock.getAttributes( + nameMock, attributeNames), expectedAttributes); + dirContextMock.close(); + + Object expected = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expected); + + replay(); + + Object actual = tested.lookup(nameMock, attributeNames, + attributesMapperMock); + + verify(); + + assertSame(expected, actual); + } + + public void testLookup_String_ReturnAttributes_AttributesMapper() + throws Exception { + expectGetReadOnlyContext(); + + String[] attributeNames = new String[] { "cn" }; + + BasicAttributes expectedAttributes = new BasicAttributes(); + expectedAttributes.put("cn", "Some Name"); + + dirContextControl.expectAndReturn(dirContextMock.getAttributes( + DEFAULT_BASE_STRING, attributeNames), expectedAttributes); + dirContextMock.close(); + + Object expected = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expected); + + replay(); + + Object actual = tested.lookup(DEFAULT_BASE_STRING, attributeNames, + attributesMapperMock); + + verify(); + + assertSame(expected, actual); + } + + // Tests for lookup(name, attributes, ContextMapper) + + public void testLookup_ReturnAttributes_ContextMapper() throws Exception { + expectGetReadOnlyContext(); + + String[] attributeNames = new String[] { "cn" }; + + BasicAttributes expectedAttributes = new BasicAttributes(); + expectedAttributes.put("cn", "Some Name"); + + DistinguishedName name = new DistinguishedName(DEFAULT_BASE_STRING); + DirContextAdapter adapter = new DirContextAdapter(expectedAttributes, + name); + + dirContextControl.expectAndReturn(dirContextMock.getAttributes(name, + attributeNames), expectedAttributes); + dirContextMock.close(); + + Object transformed = new Object(); + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(adapter), transformed); + + replay(); + + Object actual = tested.lookup(name, attributeNames, contextMapperMock); + + verify(); + + assertSame(transformed, actual); + } + + public void testLookup_String_ReturnAttributes_ContextMapper() + throws Exception { + expectGetReadOnlyContext(); + + String[] attributeNames = new String[] { "cn" }; + + BasicAttributes expectedAttributes = new BasicAttributes(); + expectedAttributes.put("cn", "Some Name"); + + dirContextControl.expectAndReturn(dirContextMock.getAttributes( + DEFAULT_BASE_STRING, attributeNames), expectedAttributes); + dirContextMock.close(); + + DistinguishedName name = new DistinguishedName(DEFAULT_BASE_STRING); + DirContextAdapter adapter = new DirContextAdapter(expectedAttributes, + name); + + Object transformed = new Object(); + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(adapter), transformed); + + replay(); + + Object actual = tested.lookup(DEFAULT_BASE_STRING, attributeNames, + contextMapperMock); + + verify(); + + assertSame(transformed, actual); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateRenameTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateRenameTest.java new file mode 100644 index 00000000..938d7c0d --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateRenameTest.java @@ -0,0 +1,171 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.Name; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.NameAlreadyBoundException; +import org.springframework.ldap.UncategorizedLdapException; + +/** + * Unit tests for the rename operations in the LdapTemplate class. + * + * @author Ulrik Sandberg + */ +public class LdapTemplateRenameTest extends TestCase { + + private MockControl contextSourceControl; + + private ContextSource contextSourceMock; + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + private MockControl oldNameControl; + + private Name oldNameMock; + + private MockControl newNameControl; + + private Name newNameMock; + + private LdapTemplate tested; + + protected void setUp() throws Exception { + super.setUp(); + + // Setup ContextSource mock + contextSourceControl = MockControl.createControl(ContextSource.class); + contextSourceMock = (ContextSource) contextSourceControl.getMock(); + + // Setup LdapContext mock + dirContextControl = MockControl.createControl(LdapContext.class); + dirContextMock = (LdapContext) dirContextControl.getMock(); + + // Setup Name mock for old name + oldNameControl = MockControl.createControl(Name.class); + oldNameMock = (Name) oldNameControl.getMock(); + + // Setup Name mock for new name + newNameControl = MockControl.createControl(Name.class); + newNameMock = (Name) newNameControl.getMock(); + + tested = new LdapTemplate(contextSourceMock); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + contextSourceControl = null; + contextSourceMock = null; + + dirContextControl = null; + dirContextMock = null; + + oldNameControl = null; + newNameMock = null; + } + + protected void replay() { + contextSourceControl.replay(); + dirContextControl.replay(); + oldNameControl.replay(); + } + + protected void verify() { + contextSourceControl.verify(); + dirContextControl.verify(); + oldNameControl.verify(); + } + + private void expectGetReadWriteContext() { + contextSourceControl.expectAndReturn(contextSourceMock + .getReadWriteContext(), dirContextMock); + } + + public void testRename() throws Exception { + expectGetReadWriteContext(); + + dirContextMock.rename(oldNameMock, newNameMock); + dirContextMock.close(); + + replay(); + + tested.rename(oldNameMock, newNameMock); + + verify(); + } + + public void testRename_NameAlreadyBoundException() throws Exception { + expectGetReadWriteContext(); + + dirContextMock.rename(oldNameMock, newNameMock); + javax.naming.NameAlreadyBoundException ne = new javax.naming.NameAlreadyBoundException(); + dirContextControl.setThrowable(ne); + dirContextMock.close(); + + replay(); + + try { + tested.rename(oldNameMock, newNameMock); + fail("NameAlreadyBoundException expected"); + } catch (NameAlreadyBoundException expected) { + assertTrue(true); + } + + verify(); + } + + public void testRename_NamingException() throws Exception { + expectGetReadWriteContext(); + + dirContextMock.rename(oldNameMock, newNameMock); + javax.naming.NamingException ne = new javax.naming.NamingException(); + dirContextControl.setThrowable(ne); + dirContextMock.close(); + + replay(); + + try { + tested.rename(oldNameMock, newNameMock); + fail("UncategorizedLdapException expected"); + } catch (UncategorizedLdapException expected) { + assertTrue(true); + } + + verify(); + } + + public void testRename_String() throws Exception { + expectGetReadWriteContext(); + + dirContextMock.rename("o=example.com", "o=somethingelse.com"); + dirContextMock.close(); + + replay(); + + tested.rename("o=example.com", "o=somethingelse.com"); + + verify(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java new file mode 100644 index 00000000..2b69f7a3 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java @@ -0,0 +1,1785 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import java.util.List; + +import javax.naming.Binding; +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.LdapContext; + +import junit.framework.TestCase; + +import org.easymock.AbstractMatcher; +import org.easymock.MockControl; +import org.springframework.ldap.LimitExceededException; +import org.springframework.ldap.NameNotFoundException; +import org.springframework.ldap.PartialResultException; + +public class LdapTemplateTest extends TestCase { + + private static final String DEFAULT_BASE_STRING = "o=example.com"; + + private MockControl contextSourceControl; + + private ContextSource contextSourceMock; + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + private MockControl attributesMapperControl; + + private AttributesMapper attributesMapperMock; + + private MockControl namingEnumerationControl; + + private NamingEnumeration namingEnumerationMock; + + private MockControl nameControl; + + private Name nameMock; + + private MockControl handlerControl; + + private NameClassPairCallbackHandler handlerMock; + + private MockControl contextMapperControl; + + private ContextMapper contextMapperMock; + + private MockControl contextExecutorControl; + + private ContextExecutor contextExecutorMock; + + private MockControl searchExecutorControl; + + private SearchExecutor searchExecutorMock; + + private LdapTemplate tested; + + private MockControl dirContextProcessorControl; + + private DirContextProcessor dirContextProcessorMock; + + private MockControl dirContextOperationsConrol; + + private DirContextOperations dirContextOperationsMock; + + protected void setUp() throws Exception { + super.setUp(); + + // Setup ContextSource mock + contextSourceControl = MockControl.createControl(ContextSource.class); + contextSourceMock = (ContextSource) contextSourceControl.getMock(); + + // Setup LdapContext mock + dirContextControl = MockControl.createControl(LdapContext.class); + dirContextMock = (LdapContext) dirContextControl.getMock(); + + // Setup NamingEnumeration mock + namingEnumerationControl = MockControl + .createControl(NamingEnumeration.class); + namingEnumerationMock = (NamingEnumeration) namingEnumerationControl + .getMock(); + + // Setup Name mock + nameControl = MockControl.createControl(Name.class); + nameMock = (Name) nameControl.getMock(); + + // Setup Handler mock + handlerControl = MockControl + .createControl(NameClassPairCallbackHandler.class); + handlerMock = (NameClassPairCallbackHandler) handlerControl.getMock(); + + contextMapperControl = MockControl.createControl(ContextMapper.class); + contextMapperMock = (ContextMapper) contextMapperControl.getMock(); + + attributesMapperControl = MockControl + .createControl(AttributesMapper.class); + attributesMapperMock = (AttributesMapper) attributesMapperControl + .getMock(); + + contextExecutorControl = MockControl + .createControl(ContextExecutor.class); + contextExecutorMock = (ContextExecutor) contextExecutorControl + .getMock(); + + searchExecutorControl = MockControl.createControl(SearchExecutor.class); + searchExecutorMock = (SearchExecutor) searchExecutorControl.getMock(); + + dirContextProcessorControl = MockControl + .createControl(DirContextProcessor.class); + dirContextProcessorMock = (DirContextProcessor) dirContextProcessorControl + .getMock(); + + dirContextOperationsConrol = MockControl + .createControl(DirContextOperations.class); + dirContextOperationsMock = (DirContextOperations) dirContextOperationsConrol + .getMock(); + + tested = new LdapTemplate(contextSourceMock); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + contextSourceControl = null; + contextSourceMock = null; + + dirContextControl = null; + dirContextMock = null; + + namingEnumerationControl = null; + namingEnumerationMock = null; + + nameControl = null; + nameMock = null; + + handlerControl = null; + handlerMock = null; + + contextMapperControl = null; + contextMapperMock = null; + + attributesMapperControl = null; + attributesMapperMock = null; + + contextExecutorControl = null; + contextExecutorMock = null; + + searchExecutorControl = null; + searchExecutorMock = null; + + dirContextProcessorControl = null; + dirContextProcessorMock = null; + + dirContextOperationsConrol = null; + dirContextOperationsMock = null; + + } + + protected void replay() { + contextSourceControl.replay(); + dirContextControl.replay(); + namingEnumerationControl.replay(); + nameControl.replay(); + handlerControl.replay(); + contextMapperControl.replay(); + attributesMapperControl.replay(); + contextExecutorControl.replay(); + searchExecutorControl.replay(); + dirContextProcessorControl.replay(); + dirContextOperationsConrol.replay(); + } + + protected void verify() { + contextSourceControl.verify(); + dirContextControl.verify(); + namingEnumerationControl.verify(); + nameControl.verify(); + handlerControl.verify(); + contextMapperControl.verify(); + attributesMapperControl.verify(); + contextExecutorControl.verify(); + searchExecutorControl.verify(); + dirContextProcessorControl.verify(); + dirContextOperationsConrol.verify(); + } + + private void expectGetReadWriteContext() { + contextSourceControl.expectAndReturn(contextSourceMock + .getReadWriteContext(), dirContextMock); + } + + private void expectGetReadOnlyContext() { + contextSourceControl.expectAndReturn(contextSourceMock + .getReadOnlyContext(), dirContextMock); + } + + public void testSearch_CallbackHandler() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(true); + + SearchResult searchResult = new SearchResult("", new Object(), + new BasicAttributes()); + + setupSearchAndNamingEnumeration(controls, searchResult); + + handlerMock.handleNameClassPair(searchResult); + + dirContextMock.close(); + + replay(); + + tested.search(nameMock, "(ou=somevalue)", 1, true, handlerMock); + + verify(); + } + + public void testSearch_StringBase_CallbackHandler() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(true); + + SearchResult searchResult = new SearchResult("", new Object(), + new BasicAttributes()); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + handlerMock.handleNameClassPair(searchResult); + + dirContextMock.close(); + + replay(); + + tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", 1, true, + handlerMock); + + verify(); + } + + private void setupStringSearchAndNamingEnumeration(SearchControls controls, + SearchResult searchResult) throws Exception { + dirContextControl.setDefaultMatcher(new SearchControlsMatcher()); + dirContextControl.expectAndReturn(dirContextMock.search( + DEFAULT_BASE_STRING, "(ou=somevalue)", controls), + namingEnumerationMock); + + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), true); + namingEnumerationControl.expectAndReturn(namingEnumerationMock.next(), + searchResult); + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), false); + namingEnumerationMock.close(); + } + + public void testSearch_CallbackHandler_Defaults() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(false); + + SearchResult searchResult = new SearchResult("", new Object(), + new BasicAttributes()); + + setupSearchAndNamingEnumeration(controls, searchResult); + + handlerMock.handleNameClassPair(searchResult); + + dirContextMock.close(); + + replay(); + + tested.search(nameMock, "(ou=somevalue)", handlerMock); + + verify(); + } + + public void testSearch_String_CallbackHandler_Defaults() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(false); + + SearchResult searchResult = new SearchResult("", new Object(), + new BasicAttributes()); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + handlerMock.handleNameClassPair(searchResult); + + dirContextMock.close(); + + replay(); + + tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", handlerMock); + + verify(); + } + + private void setupSearchAndNamingEnumeration(SearchControls controls, + SearchResult searchResult) throws Exception { + dirContextControl.setDefaultMatcher(new SearchControlsMatcher()); + dirContextControl.expectAndReturn(dirContextMock.search(nameMock, + "(ou=somevalue)", controls), namingEnumerationMock); + + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), true); + namingEnumerationControl.expectAndReturn(namingEnumerationMock.next(), + searchResult); + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), false); + namingEnumerationMock.close(); + } + + public void testSearch_NameNotFoundException() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(false); + + dirContextControl.setDefaultMatcher(new SearchControlsMatcher()); + javax.naming.NameNotFoundException ne = new javax.naming.NameNotFoundException(); + dirContextControl.expectAndThrow(dirContextMock.search(nameMock, + "(ou=somevalue)", controls), ne); + + dirContextMock.close(); + + replay(); + + tested.search(nameMock, "(ou=somevalue)", handlerMock); + + verify(); + } + + public void testSearch_NamingException() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(false); + + dirContextControl.setDefaultMatcher(new SearchControlsMatcher()); + javax.naming.LimitExceededException ne = new javax.naming.LimitExceededException(); + dirContextControl.expectAndThrow(dirContextMock.search(nameMock, + "(ou=somevalue)", controls), ne); + + dirContextMock.close(); + + replay(); + + try { + tested.search(nameMock, "(ou=somevalue)", handlerMock); + fail("LimitExceededException expected"); + } catch (LimitExceededException expected) { + // expected + } + + verify(); + } + + public void testSearch_CallbackHandler_DirContextProcessor() + throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(false); + + SearchResult searchResult = new SearchResult("", new Object(), + new BasicAttributes()); + + dirContextProcessorMock.preProcess(dirContextMock); + + setupSearchAndNamingEnumeration(controls, searchResult); + + handlerMock.handleNameClassPair(searchResult); + + dirContextProcessorMock.postProcess(dirContextMock); + + dirContextMock.close(); + + replay(); + + tested.search(nameMock, "(ou=somevalue)", controls, handlerMock, + dirContextProcessorMock); + + verify(); + } + + public void testSearch_String_CallbackHandler_DirContextProcessor() + throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(false); + + SearchResult searchResult = new SearchResult("", new Object(), + new BasicAttributes()); + + dirContextProcessorMock.preProcess(dirContextMock); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + handlerMock.handleNameClassPair(searchResult); + + dirContextProcessorMock.postProcess(dirContextMock); + + dirContextMock.close(); + + replay(); + + tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", controls, + handlerMock, dirContextProcessorMock); + + verify(); + } + + public void testSearch_String_AttributesMapper_DirContextProcessor() + throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(false); + + BasicAttributes expectedAttributes = new BasicAttributes(); + SearchResult searchResult = new SearchResult("", null, + expectedAttributes); + + dirContextProcessorMock.preProcess(dirContextMock); + setupStringSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expectedResult); + + dirContextProcessorMock.postProcess(dirContextMock); + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", + controls, attributesMapperMock, dirContextProcessorMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_Name_AttributesMapper_DirContextProcessor() + throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(false); + + BasicAttributes expectedAttributes = new BasicAttributes(); + SearchResult searchResult = new SearchResult("", null, + expectedAttributes); + + dirContextProcessorMock.preProcess(dirContextMock); + setupSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expectedResult); + + dirContextProcessorMock.postProcess(dirContextMock); + dirContextMock.close(); + + replay(); + + List list = tested.search(nameMock, "(ou=somevalue)", controls, + attributesMapperMock, dirContextProcessorMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_String_SearchControls_ContextMapper_DirContextProcessor() + throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(true); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + dirContextProcessorMock.preProcess(dirContextMock); + setupStringSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextProcessorMock.postProcess(dirContextMock); + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", + controls, contextMapperMock, dirContextProcessorMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_Name_SearchControls_ContextMapper_DirContextProcessor() + throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(true); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + dirContextProcessorMock.preProcess(dirContextMock); + setupSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextProcessorMock.postProcess(dirContextMock); + dirContextMock.close(); + + replay(); + + List list = tested.search(nameMock, "(ou=somevalue)", controls, + contextMapperMock, dirContextProcessorMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_AttributesMapper_ReturningAttrs() throws Exception { + expectGetReadOnlyContext(); + + String[] attrs = new String[0]; + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(false); + controls.setReturningAttributes(attrs); + + BasicAttributes expectedAttributes = new BasicAttributes(); + SearchResult searchResult = new SearchResult("", null, + expectedAttributes); + + setupSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(nameMock, "(ou=somevalue)", 1, attrs, + attributesMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_String_AttributesMapper_ReturningAttrs() + throws Exception { + expectGetReadOnlyContext(); + + String[] attrs = new String[0]; + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(false); + controls.setReturningAttributes(attrs); + + BasicAttributes expectedAttributes = new BasicAttributes(); + SearchResult searchResult = new SearchResult("", null, + expectedAttributes); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", 1, + attrs, attributesMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_AttributesMapper() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(false); + + BasicAttributes expectedAttributes = new BasicAttributes(); + SearchResult searchResult = new SearchResult("", null, + expectedAttributes); + + setupSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(nameMock, "(ou=somevalue)", 1, + attributesMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_String_AttributesMapper() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(false); + + BasicAttributes expectedAttributes = new BasicAttributes(); + SearchResult searchResult = new SearchResult("", null, + expectedAttributes); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", 1, + attributesMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_AttributesMapper_Default() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(false); + + BasicAttributes expectedAttributes = new BasicAttributes(); + SearchResult searchResult = new SearchResult("", null, + expectedAttributes); + + setupSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(nameMock, "(ou=somevalue)", + attributesMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_String_AttributesMapper_Default() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(false); + + BasicAttributes expectedAttributes = new BasicAttributes(); + SearchResult searchResult = new SearchResult("", null, + expectedAttributes); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", + attributesMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_ContextMapper() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(true); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + setupSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(nameMock, "(ou=somevalue)", 1, + contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_ContextMapper_ReturningAttrs() throws Exception { + expectGetReadOnlyContext(); + + String[] attrs = new String[0]; + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(true); + controls.setReturningAttributes(attrs); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + setupSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(nameMock, "(ou=somevalue)", 1, attrs, + contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_String_ContextMapper_ReturningAttrs() + throws Exception { + expectGetReadOnlyContext(); + + String[] attrs = new String[0]; + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(true); + controls.setReturningAttributes(attrs); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", 1, + attrs, contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_String_ContextMapper() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(true); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", 1, + contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_ContextMapper_Default() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(true); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + setupSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested + .search(nameMock, "(ou=somevalue)", contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_String_ContextMapper_Default() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(true); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", + contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_String_SearchControls_ContextMapper() + throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(true); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", + controls, contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_String_SearchControls_ContextMapper_ReturningObjFlagNotSet() + throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + SearchControls expectedControls = new SearchControls(); + expectedControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + expectedControls.setReturningObjFlag(true); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + setupStringSearchAndNamingEnumeration(expectedControls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", + controls, contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_Name_SearchControls_ContextMapper() throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + controls.setReturningObjFlag(true); + + Object expectedObject = new Object(); + SearchResult searchResult = new SearchResult("", expectedObject, + new BasicAttributes()); + + setupSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = expectedObject; + contextMapperControl.expectAndReturn(contextMapperMock + .mapFromContext(expectedObject), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(nameMock, "(ou=somevalue)", controls, + contextMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_String_SearchControls_AttributesMapper() + throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(false); + + BasicAttributes expectedAttributes = new BasicAttributes(); + SearchResult searchResult = new SearchResult("", null, + expectedAttributes); + + setupStringSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(DEFAULT_BASE_STRING, "(ou=somevalue)", + controls, attributesMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testSearch_Name_SearchControls_AttributesMapper() + throws Exception { + expectGetReadOnlyContext(); + + SearchControls controls = new SearchControls(); + controls.setSearchScope(1); + controls.setReturningObjFlag(false); + + BasicAttributes expectedAttributes = new BasicAttributes(); + SearchResult searchResult = new SearchResult("", null, + expectedAttributes); + + setupSearchAndNamingEnumeration(controls, searchResult); + + Object expectedResult = new Object(); + attributesMapperControl.expectAndReturn(attributesMapperMock + .mapFromAttributes(expectedAttributes), expectedResult); + + dirContextMock.close(); + + replay(); + + List list = tested.search(nameMock, "(ou=somevalue)", controls, + attributesMapperMock); + + verify(); + + assertNotNull(list); + assertEquals(1, list.size()); + assertSame(expectedResult, list.get(0)); + } + + public void testModifyAttributes() throws Exception { + expectGetReadWriteContext(); + + ModificationItem[] mods = new ModificationItem[0]; + dirContextMock.modifyAttributes(nameMock, mods); + + dirContextMock.close(); + + replay(); + + tested.modifyAttributes(nameMock, mods); + + verify(); + } + + public void testModifyAttributes_String() throws Exception { + expectGetReadWriteContext(); + + ModificationItem[] mods = new ModificationItem[0]; + dirContextMock.modifyAttributes(DEFAULT_BASE_STRING, mods); + + dirContextMock.close(); + + replay(); + + tested.modifyAttributes(DEFAULT_BASE_STRING, mods); + + verify(); + } + + public void testModifyAttributes_NamingException() throws Exception { + expectGetReadWriteContext(); + + ModificationItem[] mods = new ModificationItem[0]; + dirContextMock.modifyAttributes(nameMock, mods); + javax.naming.LimitExceededException ne = new javax.naming.LimitExceededException(); + dirContextControl.setThrowable(ne); + + dirContextMock.close(); + + replay(); + + try { + tested.modifyAttributes(nameMock, mods); + fail("LimitExceededException expected"); + } catch (LimitExceededException expected) { + assertTrue(true); + } + + verify(); + } + + public void testBind() throws Exception { + expectGetReadWriteContext(); + + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + dirContextMock.bind(nameMock, expectedObject, expectedAttributes); + dirContextMock.close(); + + replay(); + + tested.bind(nameMock, expectedObject, expectedAttributes); + + verify(); + + } + + public void testBind_String() throws Exception { + expectGetReadWriteContext(); + + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + dirContextMock.bind(DEFAULT_BASE_STRING, expectedObject, + expectedAttributes); + dirContextMock.close(); + + replay(); + + tested.bind(DEFAULT_BASE_STRING, expectedObject, expectedAttributes); + + verify(); + + } + + public void testBind_NamingException() throws Exception { + expectGetReadWriteContext(); + + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + dirContextMock.bind(nameMock, expectedObject, expectedAttributes); + javax.naming.NameNotFoundException ne = new javax.naming.NameNotFoundException(); + dirContextControl.setThrowable(ne); + dirContextMock.close(); + + replay(); + + try { + tested.bind(nameMock, expectedObject, expectedAttributes); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + verify(); + + } + + public void testUnbind() throws Exception { + expectGetReadWriteContext(); + + dirContextMock.unbind(nameMock); + dirContextMock.close(); + replay(); + + tested.unbind(nameMock); + + verify(); + } + + public void testUnbind_String() throws Exception { + expectGetReadWriteContext(); + + dirContextMock.unbind(DEFAULT_BASE_STRING); + dirContextMock.close(); + replay(); + + tested.unbind(DEFAULT_BASE_STRING); + + verify(); + } + + public void testUnbindRecursive() throws Exception { + expectGetReadWriteContext(); + + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), true); + Binding binding = new Binding("cn=Some name", null); + namingEnumerationControl.expectAndReturn(namingEnumerationMock.next(), + binding); + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), false); + namingEnumerationMock.close(); + + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), false); + namingEnumerationMock.close(); + + DistinguishedName listDn = new DistinguishedName(DEFAULT_BASE_STRING); + dirContextMock.listBindings(listDn); + dirContextControl.setReturnValue(namingEnumerationMock); + DistinguishedName subListDn = new DistinguishedName( + "cn=Some name, o=example.com"); + dirContextMock.listBindings(subListDn); + dirContextControl.setReturnValue(namingEnumerationMock); + + dirContextMock.unbind(subListDn); + dirContextMock.unbind(listDn); + dirContextMock.close(); + + // Caused by creating a DistinguishedName from a Name + nameControl.expectAndReturn(nameMock.size(), 1, 2); + nameControl.expectAndReturn(nameMock.get(0), "o=example.com"); + + replay(); + + tested.unbind(nameMock, true); + + verify(); + } + + public void testUnbindRecursive_String() throws Exception { + expectGetReadWriteContext(); + + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), true); + Binding binding = new Binding("cn=Some name", null); + namingEnumerationControl.expectAndReturn(namingEnumerationMock.next(), + binding); + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), false); + namingEnumerationMock.close(); + + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), false); + namingEnumerationMock.close(); + + DistinguishedName listDn = new DistinguishedName(DEFAULT_BASE_STRING); + dirContextMock.listBindings(listDn); + dirContextControl.setReturnValue(namingEnumerationMock); + DistinguishedName subListDn = new DistinguishedName( + "cn=Some name, o=example.com"); + dirContextMock.listBindings(subListDn); + dirContextControl.setReturnValue(namingEnumerationMock); + + dirContextMock.unbind(subListDn); + dirContextMock.unbind(listDn); + dirContextMock.close(); + replay(); + + tested.unbind(DEFAULT_BASE_STRING, true); + + verify(); + } + + public void testRebind() throws Exception { + expectGetReadWriteContext(); + + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + dirContextMock.rebind(nameMock, expectedObject, expectedAttributes); + + dirContextMock.close(); + + replay(); + + tested.rebind(nameMock, expectedObject, expectedAttributes); + + verify(); + } + + public void testRebind_String() throws Exception { + expectGetReadWriteContext(); + + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + dirContextMock.rebind(DEFAULT_BASE_STRING, expectedObject, + expectedAttributes); + + dirContextMock.close(); + + replay(); + + tested.rebind(DEFAULT_BASE_STRING, expectedObject, expectedAttributes); + + verify(); + } + + public void testUnbind_NamingException() throws Exception { + expectGetReadWriteContext(); + + dirContextMock.unbind(nameMock); + javax.naming.NameNotFoundException ne = new javax.naming.NameNotFoundException(); + dirContextControl.setThrowable(ne); + dirContextMock.close(); + + replay(); + + try { + tested.unbind(nameMock); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + verify(); + } + + public void testExecuteReadOnly() throws Exception { + expectGetReadOnlyContext(); + + Object object = new Object(); + contextExecutorControl.expectAndReturn(contextExecutorMock + .executeWithContext(dirContextMock), object); + + dirContextMock.close(); + + replay(); + + Object result = tested.executeReadOnly(contextExecutorMock); + + verify(); + + assertSame(object, result); + } + + public void testExecuteReadOnly_NamingException() throws Exception { + expectGetReadOnlyContext(); + + javax.naming.NameNotFoundException ne = new javax.naming.NameNotFoundException(); + contextExecutorControl.expectAndThrow(contextExecutorMock + .executeWithContext(dirContextMock), ne); + + dirContextMock.close(); + + replay(); + + try { + tested.executeReadOnly(contextExecutorMock); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + verify(); + } + + public void testExecuteReadWrite() throws Exception { + expectGetReadWriteContext(); + + Object object = new Object(); + contextExecutorControl.expectAndReturn(contextExecutorMock + .executeWithContext(dirContextMock), object); + + dirContextMock.close(); + + replay(); + + Object result = tested.executeReadWrite(contextExecutorMock); + + verify(); + + assertSame(object, result); + } + + public void testExecuteReadWrite_NamingException() throws Exception { + expectGetReadWriteContext(); + + javax.naming.NameNotFoundException ne = new javax.naming.NameNotFoundException(); + contextExecutorControl.expectAndThrow(contextExecutorMock + .executeWithContext(dirContextMock), ne); + + dirContextMock.close(); + + replay(); + + try { + tested.executeReadWrite(contextExecutorMock); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + verify(); + } + + public void testDoSearch_DirContextProcessor() throws Exception { + expectGetReadOnlyContext(); + + SearchResult searchResult = new SearchResult(null, null, null); + + dirContextProcessorMock.preProcess(dirContextMock); + + searchExecutorControl.expectAndReturn(searchExecutorMock + .executeSearch(dirContextMock), namingEnumerationMock); + + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), true); + namingEnumerationControl.expectAndReturn(namingEnumerationMock.next(), + searchResult); + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), false); + namingEnumerationMock.close(); + + handlerMock.handleNameClassPair(searchResult); + + dirContextProcessorMock.postProcess(dirContextMock); + + dirContextMock.close(); + + replay(); + + tested.search(searchExecutorMock, handlerMock, dirContextProcessorMock); + + verify(); + } + + public void testDoSearch_DirContextProcessor_NamingException() + throws Exception { + expectGetReadOnlyContext(); + + dirContextProcessorMock.preProcess(dirContextMock); + + javax.naming.LimitExceededException ne = new javax.naming.LimitExceededException(); + searchExecutorControl.expectAndThrow(searchExecutorMock + .executeSearch(dirContextMock), ne); + + dirContextProcessorMock.postProcess(dirContextMock); + dirContextMock.close(); + + replay(); + + try { + tested.search(searchExecutorMock, handlerMock, + dirContextProcessorMock); + fail("LimitExceededException expected"); + } catch (LimitExceededException expected) { + assertTrue(true); + } + + verify(); + } + + public void testDoSearch() throws Exception { + expectGetReadOnlyContext(); + + SearchResult searchResult = new SearchResult(null, null, null); + + searchExecutorControl.expectAndReturn(searchExecutorMock + .executeSearch(dirContextMock), namingEnumerationMock); + + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), true); + namingEnumerationControl.expectAndReturn(namingEnumerationMock.next(), + searchResult); + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), false); + namingEnumerationMock.close(); + + handlerMock.handleNameClassPair(searchResult); + + dirContextMock.close(); + + replay(); + + tested.search(searchExecutorMock, handlerMock); + + verify(); + } + + public void testDoSearch_NamingException() throws Exception { + expectGetReadOnlyContext(); + + javax.naming.LimitExceededException ne = new javax.naming.LimitExceededException(); + searchExecutorControl.expectAndThrow(searchExecutorMock + .executeSearch(dirContextMock), ne); + + dirContextMock.close(); + + replay(); + + try { + tested.search(searchExecutorMock, handlerMock); + fail("LimitExceededException expected"); + } catch (LimitExceededException expected) { + assertTrue(true); + } + + verify(); + } + + public void testDoSearch_NamingException_NamingEnumeration() + throws Exception { + expectGetReadOnlyContext(); + + searchExecutorControl.expectAndReturn(searchExecutorMock + .executeSearch(dirContextMock), namingEnumerationMock); + + javax.naming.LimitExceededException ne = new javax.naming.LimitExceededException(); + namingEnumerationControl.expectAndThrow( + namingEnumerationMock.hasMore(), ne); + namingEnumerationMock.close(); + + dirContextMock.close(); + + replay(); + + try { + tested.search(searchExecutorMock, handlerMock); + fail("LimitExceededException expected"); + } catch (LimitExceededException expected) { + assertTrue(true); + } + + verify(); + } + + public void testDoSearch_NameNotFoundException() throws Exception { + expectGetReadOnlyContext(); + + searchExecutorControl.expectAndThrow(searchExecutorMock + .executeSearch(dirContextMock), + new javax.naming.NameNotFoundException()); + dirContextMock.close(); + + replay(); + + tested.search(searchExecutorMock, handlerMock); + + verify(); + } + + public void testSearch_PartialResult_IgnoreNotSet() throws Exception { + expectGetReadOnlyContext(); + + dirContextProcessorMock.preProcess(dirContextMock); + + javax.naming.PartialResultException ex = new javax.naming.PartialResultException(); + searchExecutorControl.expectAndThrow(searchExecutorMock + .executeSearch(dirContextMock), ex); + dirContextProcessorMock.postProcess(dirContextMock); + dirContextMock.close(); + + replay(); + + try { + tested.search(searchExecutorMock, handlerMock, + dirContextProcessorMock); + fail("PartialResultException expected"); + } catch (PartialResultException expected) { + assertTrue(true); + } + + verify(); + } + + public void testSearch_PartialResult_IgnoreSet() throws Exception { + tested.setIgnorePartialResultException(true); + + expectGetReadOnlyContext(); + + dirContextProcessorMock.preProcess(dirContextMock); + + searchExecutorControl.expectAndThrow(searchExecutorMock + .executeSearch(dirContextMock), + new javax.naming.PartialResultException()); + + dirContextProcessorMock.postProcess(dirContextMock); + dirContextMock.close(); + + replay(); + + tested.search(searchExecutorMock, handlerMock, dirContextProcessorMock); + + verify(); + } + + public void testLookupContextWithName() { + final DirContextAdapter expectedResult = new DirContextAdapter(); + + LdapTemplate tested = new LdapTemplate() { + public Object lookup(Name dn) { + assertSame(DistinguishedName.EMPTY_PATH, dn); + return expectedResult; + } + }; + + DirContextOperations result = tested + .lookupContext(DistinguishedName.EMPTY_PATH); + assertSame(expectedResult, result); + + } + + public void testLookupContextWithString() { + final DirContextAdapter expectedResult = new DirContextAdapter(); + final String expectedName = "cn=John Doe"; + + LdapTemplate tested = new LdapTemplate() { + public Object lookup(String dn) { + assertSame(expectedName, dn); + return expectedResult; + } + }; + + DirContextOperations result = tested.lookupContext(expectedName); + assertSame(expectedResult, result); + } + + public void testModifyAttributesWithDirContextOperations() throws Exception { + final ModificationItem[] expectedModifications = new ModificationItem[0]; + + dirContextOperationsConrol.expectAndReturn(dirContextOperationsMock + .getDn(), DistinguishedName.EMPTY_PATH); + dirContextOperationsConrol.expectAndReturn(dirContextOperationsMock + .isUpdateMode(), true); + dirContextOperationsConrol.expectAndReturn(dirContextOperationsMock + .getModificationItems(), expectedModifications); + + LdapTemplate tested = new LdapTemplate() { + public void modifyAttributes(Name dn, ModificationItem[] mods) { + assertSame(DistinguishedName.EMPTY_PATH, dn); + assertSame(expectedModifications, mods); + } + }; + + replay(); + + tested.modifyAttributes(dirContextOperationsMock); + + verify(); + } + + public void testModifyAttributesWithDirContextOperationsNotInitializedDn() + throws Exception { + + dirContextOperationsConrol.expectAndReturn(dirContextOperationsMock + .getDn(), DistinguishedName.EMPTY_PATH); + dirContextOperationsConrol.expectAndReturn(dirContextOperationsMock + .isUpdateMode(), false); + + LdapTemplate tested = new LdapTemplate() { + public void modifyAttributes(Name dn, ModificationItem[] mods) { + fail("The call to the base modifyAttributes should not have occured."); + } + }; + + replay(); + + try { + tested.modifyAttributes(dirContextOperationsMock); + fail("IllegalStateException expected"); + } catch (IllegalStateException expected) { + assertTrue(true); + } + verify(); + } + + public void testModifyAttributesWithDirContextOperationsNotInitializedInUpdateMode() + throws Exception { + + dirContextOperationsConrol.expectAndReturn(dirContextOperationsMock + .getDn(), null); + + LdapTemplate tested = new LdapTemplate() { + public void modifyAttributes(Name dn, ModificationItem[] mods) { + fail("The call to the base modifyAttributes should not have occured."); + } + }; + + replay(); + + try { + tested.modifyAttributes(dirContextOperationsMock); + fail("IllegalStateException expected"); + } catch (IllegalStateException expected) { + assertTrue(true); + } + verify(); + } + + /** + * Needed to verify search control values. + * + * @author Mattias Arthursson + */ + private static class SearchControlsMatcher extends AbstractMatcher { + protected boolean argumentMatches(Object arg0, Object arg1) { + if (arg0 instanceof SearchControls + && arg1 instanceof SearchControls) { + SearchControls s0 = (SearchControls) arg0; + SearchControls s1 = (SearchControls) arg1; + + return s0.getSearchScope() == s1.getSearchScope() + && s0.getReturningObjFlag() == s1.getReturningObjFlag() + && s0.getDerefLinkFlag() == s1.getDerefLinkFlag() + && s0.getCountLimit() == s1.getCountLimit() + && s0.getTimeLimit() == s1.getTimeLimit() + && s0.getReturningAttributes() == s1 + .getReturningAttributes(); + } else { + return super.argumentMatches(arg0, arg1); + } + } + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/RequestControlMatcher.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/RequestControlMatcher.java new file mode 100644 index 00000000..fdbdf6f5 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/RequestControlMatcher.java @@ -0,0 +1,72 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.ldap.Control; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.easymock.AbstractMatcher; + +/** + * Custom argument matcher that matches javax.naming.ldap.Control objects. + * + * @author Adam Skogman + */ +public class RequestControlMatcher extends AbstractMatcher { + + /** + * @see org.easymock.AbstractMatcher#argumentMatches(java.lang.Object, + * java.lang.Object) + */ + protected boolean argumentMatches(Object expected, Object actual) { + + // null checks + if (expected == null && actual == null) { + return true; + } + if (expected == null || actual == null) { + return false; + } + + // Both params should be arrays + Object[] expArray = (Object[]) expected; + Object[] actArray = (Object[]) actual; + + if (expArray.length != actArray.length) { + return false; + } + + // Compary each object + for (int i = 0; i < expArray.length; i++) { + if (!controlMatches((Control) expArray[i], (Control) actArray[i])) { + return false; + } + } + return true; + + } + + private boolean controlMatches(Control expected, Control actual) { + + // Compare SortControl + return StringUtils.equals(expected.getID(), actual.getID()) + && expected.isCritical() == actual.isCritical() + && ArrayUtils.isEquals(expected.getEncodedValue(), actual + .getEncodedValue()); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/support/AggregateDirContextProcessorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/support/AggregateDirContextProcessorTest.java new file mode 100644 index 00000000..79b4f26f --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/support/AggregateDirContextProcessorTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import javax.naming.NamingException; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.DirContextProcessor; +import org.springframework.ldap.core.support.AggregateDirContextProcessor; + +public class AggregateDirContextProcessorTest extends TestCase { + + private MockControl processor1Control; + + private DirContextProcessor processor1Mock; + + private MockControl processor2Control; + + private DirContextProcessor processor2Mock; + + private AggregateDirContextProcessor tested; + + protected void setUp() throws Exception { + super.setUp(); + + // Create processor1 mock + processor1Control = MockControl + .createControl(DirContextProcessor.class); + processor1Mock = (DirContextProcessor) processor1Control.getMock(); + + // Create processor2 mock + processor2Control = MockControl + .createControl(DirContextProcessor.class); + processor2Mock = (DirContextProcessor) processor2Control.getMock(); + + tested = new AggregateDirContextProcessor(); + tested.addDirContextProcessor(processor1Mock); + tested.addDirContextProcessor(processor2Mock); + + } + + protected void tearDown() throws Exception { + super.tearDown(); + + processor1Control = null; + processor1Mock = null; + + processor2Control = null; + processor2Mock = null; + } + + protected void replay() { + processor1Control.replay(); + processor2Control.replay(); + + } + + protected void verify() { + processor1Control.verify(); + processor2Control.verify(); + } + + public void testPreProcess() throws NamingException { + processor1Mock.preProcess(null); + processor2Mock.preProcess(null); + + replay(); + + tested.preProcess(null); + + verify(); + } + + public void testPostProcess() throws NamingException { + processor1Mock.postProcess(null); + processor2Mock.postProcess(null); + + replay(); + + tested.postProcess(null); + + verify(); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostProcessorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostProcessorTest.java new file mode 100644 index 00000000..6a64d39b --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostProcessorTest.java @@ -0,0 +1,159 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import org.easymock.MockControl; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; +import org.springframework.ldap.core.DistinguishedName; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link BaseLdapPathBeanPostProcessor}. + * + * @author Mattias Arthursson + */ +public class BaseLdapPathBeanPostProcessorTest extends TestCase { + + private BaseLdapPathBeanPostProcessor tested; + + private MockControl ldapPathAwareControl; + + private BaseLdapPathAware ldapPathAwareMock; + + private MockControl applicationContextControl; + + private ApplicationContext applicationContextMock; + + protected void setUp() throws Exception { + tested = new BaseLdapPathBeanPostProcessor(); + + ldapPathAwareControl = MockControl.createControl(BaseLdapPathAware.class); + ldapPathAwareMock = (BaseLdapPathAware) ldapPathAwareControl.getMock(); + + applicationContextControl = MockControl.createControl(ApplicationContext.class); + applicationContextMock = (ApplicationContext) applicationContextControl.getMock(); + + tested.setApplicationContext(applicationContextMock); + } + + protected void tearDown() throws Exception { + tested = null; + ldapPathAwareControl = null; + ldapPathAwareMock = null; + + applicationContextControl = null; + applicationContextMock = null; + } + + public void testPostProcessBeforeInitializationWithLdapPathAwareBasePathSet() throws Exception { + String expectedPath = "dc=example, dc=com"; + tested.setBasePath(new DistinguishedName(expectedPath)); + + ldapPathAwareMock.setBaseLdapPath(new DistinguishedName(expectedPath)); + + ldapPathAwareControl.replay(); + + Object result = tested.postProcessBeforeInitialization(ldapPathAwareMock, "someName"); + + ldapPathAwareControl.verify(); + + assertSame(ldapPathAwareMock, result); + } + + public void testPostProcessBeforeInitializationWithLdapPathAwareNoBasePathSet() throws Exception { + final LdapContextSource expectedContextSource = new LdapContextSource(); + String expectedPath = "dc=example, dc=com"; + expectedContextSource.setBase(expectedPath); + + tested = new BaseLdapPathBeanPostProcessor() { + BaseLdapPathSource getBaseLdapPathSourceFromApplicationContext() { + return expectedContextSource; + } + }; + + ldapPathAwareMock.setBaseLdapPath(new DistinguishedName(expectedPath)); + + ldapPathAwareControl.replay(); + + Object result = tested.postProcessBeforeInitialization(ldapPathAwareMock, "someName"); + + ldapPathAwareControl.verify(); + + assertSame(ldapPathAwareMock, result); + } + + public void testGetAbstractContextSourceFromApplicationContext() throws Exception { + applicationContextControl.expectAndReturn(applicationContextMock + .getBeanNamesForType(BaseLdapPathSource.class), new String[] { "contextSource" }); + LdapContextSource expectedContextSource = new LdapContextSource(); + applicationContextControl.expectAndReturn(applicationContextMock.getBean("contextSource"), + expectedContextSource); + + applicationContextControl.replay(); + + BaseLdapPathSource result = tested.getBaseLdapPathSourceFromApplicationContext(); + + applicationContextControl.verify(); + assertSame(expectedContextSource, result); + } + + public void testGetAbstractContextSourceFromApplicationContextNoContextSource() throws Exception { + applicationContextControl.expectAndReturn(applicationContextMock + .getBeanNamesForType(BaseLdapPathSource.class), new String[0]); + + applicationContextControl.replay(); + + try { + tested.getBaseLdapPathSourceFromApplicationContext(); + fail("NoSuchBeanDefinitionException expected"); + } + catch (NoSuchBeanDefinitionException expected) { + assertTrue(true); + } + applicationContextControl.verify(); + } + + public void testGetAbstractContextSourceFromApplicationContextTwoContextSources() throws Exception { + applicationContextControl.expectAndReturn(applicationContextMock + .getBeanNamesForType(BaseLdapPathSource.class), new String[2]); + + applicationContextControl.replay(); + + try { + tested.getBaseLdapPathSourceFromApplicationContext(); + fail("NoSuchBeanDefinitionException expected"); + } + catch (NoSuchBeanDefinitionException expected) { + assertTrue(true); + } + applicationContextControl.verify(); + } + + public void testGetAbstractContextSourceFromApplicationContextTwoContextSourcesAndSpecifiedName() throws Exception { + LdapContextSource expectedContextSource = new LdapContextSource(); + + tested.setBaseLdapPathSourceName("myContextSource"); + applicationContextControl.expectAndReturn(applicationContextMock.getBean("myContextSource"), + expectedContextSource); + + applicationContextControl.replay(); + + tested.getBaseLdapPathSourceFromApplicationContext(); + applicationContextControl.verify(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/support/CountNameClassPairResultCallbackHandlerTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/support/CountNameClassPairResultCallbackHandlerTest.java new file mode 100644 index 00000000..5c056e3a --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/support/CountNameClassPairResultCallbackHandlerTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import javax.naming.directory.SearchResult; + +import org.springframework.ldap.core.support.CountNameClassPairCallbackHandler; + + +import junit.framework.TestCase; + +public class CountNameClassPairResultCallbackHandlerTest extends TestCase { + + + private CountNameClassPairCallbackHandler tested; + + protected void setUp() throws Exception { + super.setUp(); + + tested = new CountNameClassPairCallbackHandler(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + tested = null; + } + + public void testHandleSearchResult() throws Exception { + SearchResult dummy = new SearchResult(null, null, null); + tested.handleNameClassPair(dummy); + tested.handleNameClassPair(dummy); + tested.handleNameClassPair(dummy); + + assertEquals(3, tested.getNoOfRows()); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/support/DefaultDirObjectFactoryTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/support/DefaultDirObjectFactoryTest.java new file mode 100644 index 00000000..b501f103 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/support/DefaultDirObjectFactoryTest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.directory.BasicAttributes; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; + +public class DefaultDirObjectFactoryTest extends TestCase { + + private MockControl contextControl; + + private Context contextMock; + + private static final Name DN = new DistinguishedName( + "ou=some unit, dc=jayway, dc=se"); + + private DefaultDirObjectFactory tested; + + private MockControl contextControl2; + + private Context contextMock2; + + protected void setUp() throws Exception { + super.setUp(); + + contextControl = MockControl.createControl(Context.class); + contextMock = (Context) contextControl.getMock(); + + contextControl2 = MockControl.createControl(Context.class); + contextMock2 = (Context) contextControl2.getMock(); + + tested = new DefaultDirObjectFactory(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + contextControl = null; + contextMock = null; + + contextControl2 = null; + contextMock2 = null; + + tested = null; + } + + protected void replay() { + contextControl.replay(); + contextControl2.replay(); + } + + protected void verify() { + contextControl.verify(); + contextControl2.verify(); + } + + public void testGetObjectInstance() throws Exception { + BasicAttributes expectedAttributes = new BasicAttributes(); + expectedAttributes.put("someAttribute", "someValue"); + + contextMock.close(); + + replay(); + + DirContextAdapter adapter = (DirContextAdapter) tested + .getObjectInstance(contextMock, DN, null, new Hashtable(), + expectedAttributes); + + verify(); + + assertEquals(DN, adapter.getDn()); + assertEquals(expectedAttributes, adapter.getAttributes()); + } + + public void testGetObjectInstance_nullObject() throws Exception { + BasicAttributes expectedAttributes = new BasicAttributes(); + expectedAttributes.put("someAttribute", "someValue"); + + replay(); + + DirContextAdapter adapter = (DirContextAdapter) tested + .getObjectInstance(null, DN, null, new Hashtable(), + expectedAttributes); + + verify(); + + assertEquals(DN, adapter.getDn()); + assertEquals(expectedAttributes, adapter.getAttributes()); + } + + public void testGetObjectInstance_ObjectNotContext() throws Exception { + BasicAttributes expectedAttributes = new BasicAttributes(); + expectedAttributes.put("someAttribute", "someValue"); + + replay(); + + DirContextAdapter adapter = (DirContextAdapter) tested + .getObjectInstance(new Object(), DN, null, new Hashtable(), + expectedAttributes); + + verify(); + + assertEquals(DN, adapter.getDn()); + assertEquals(expectedAttributes, adapter.getAttributes()); + } + + /** + * Make sure that the base suffix is stripped off from the DN. + * + * @throws Exception + */ + public void testGetObjectInstance_BaseSet() throws Exception { + BasicAttributes expectedAttributes = new BasicAttributes(); + expectedAttributes.put("someAttribute", "someValue"); + + contextControl2.expectAndReturn(contextMock2.getNameInNamespace(), + "dc=jayway, dc=se"); + contextMock.close(); + + replay(); + + DirContextAdapter adapter = (DirContextAdapter) tested + .getObjectInstance(contextMock, new DistinguishedName( + "ou=some unit"), contextMock2, new Hashtable(), + expectedAttributes); + + verify(); + + assertEquals("ou=some unit", adapter.getDn().toString()); + assertEquals("ou=some unit, dc=jayway, dc=se", adapter + .getNameInNamespace()); + assertEquals(expectedAttributes, adapter.getAttributes()); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/core/support/LdapContextSourceTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/core/support/LdapContextSourceTest.java new file mode 100644 index 00000000..abce5c51 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/core/support/LdapContextSourceTest.java @@ -0,0 +1,288 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core.support; + +import java.util.HashMap; +import java.util.Hashtable; + +import javax.naming.Context; + +import junit.framework.TestCase; + +import org.springframework.ldap.core.AuthenticationSource; +import org.springframework.ldap.core.DistinguishedName; + +/** + * Unit tests for the LdapContextSource class. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public class LdapContextSourceTest extends TestCase { + + private LdapContextSource tested; + + protected void setUp() throws Exception { + tested = new LdapContextSource(); + } + + protected void tearDown() throws Exception { + tested = null; + } + + public void testAfterPropertiesSet_NoUrl() throws Exception { + try { + tested.afterPropertiesSet(); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testAfterPropertiesSet_BaseAndTooEarlyJdk() throws Exception { + tested = new LdapContextSource() { + String getJdkVersion() { + return "1.4.1_03"; + } + }; + + tested.setUrl("http://ldap.example.com:389"); + tested.setBase("dc=jayway,dc=se"); + try { + tested.afterPropertiesSet(); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testGetAnonymousEnv() throws Exception { + tested.setBase("dc=example,dc=se"); + tested.setUrl("ldap://ldap.example.com:389"); + tested.setPooled(true); + tested.setUserDn("cn=Some User"); + tested.setPassword("secret"); + tested.afterPropertiesSet(); + Hashtable env = tested.getAnonymousEnv(); + assertEquals("ldap://ldap.example.com:389/dc=example,dc=se", env.get(Context.PROVIDER_URL)); + assertEquals("true", env.get(LdapContextSource.SUN_LDAP_POOLING_FLAG)); + assertNull(env.get(Context.SECURITY_PRINCIPAL)); + assertNull(env.get(Context.SECURITY_CREDENTIALS)); + + // check that base was added to environment + assertEquals(new DistinguishedName("dc=example,dc=se"), env.get(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY)); + + // Verify that changing values does not change the environment values. + tested.setBase("dc=other,dc=se"); + tested.setUrl("ldap://ldap2.example.com:389"); + tested.setPooled(false); + + env = tested.getAnonymousEnv(); + assertEquals("ldap://ldap.example.com:389/dc=example,dc=se", env.get(Context.PROVIDER_URL)); + assertEquals("true", env.get(LdapContextSource.SUN_LDAP_POOLING_FLAG)); + assertNull(env.get(Context.SECURITY_PRINCIPAL)); + assertNull(env.get(Context.SECURITY_CREDENTIALS)); + + assertEquals(new DistinguishedName("dc=example,dc=se"), env.get(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY)); + } + + public void testGetAnonymousEnvWithNoBaseSet() throws Exception { + tested.setUrl("ldap://ldap.example.com:389"); + tested.afterPropertiesSet(); + Hashtable env = tested.getAnonymousEnv(); + assertEquals("ldap://ldap.example.com:389", env.get(Context.PROVIDER_URL)); + + // check that base was not added to environment + assertNull(env.get(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY)); + } + + public void testGetAnonymousEnvWithBaseEnvironment() throws Exception { + tested.setUrl("ldap://ldap.example.com:389"); + HashMap map = new HashMap(); + map.put(LdapContextSource.SUN_LDAP_POOLING_FLAG, "true"); + tested.setBaseEnvironmentProperties(map); + tested.afterPropertiesSet(); + Hashtable env = tested.getAnonymousEnv(); + assertEquals("ldap://ldap.example.com:389", env.get(Context.PROVIDER_URL)); + assertEquals("true", env.get(LdapContextSource.SUN_LDAP_POOLING_FLAG)); + } + + public void testGetAnonymousEnvWithPoolingInBaseEnvironmentAndPoolingOff() throws Exception { + tested.setUrl("ldap://ldap.example.com:389"); + HashMap map = new HashMap(); + map.put(LdapContextSource.SUN_LDAP_POOLING_FLAG, "true"); + tested.setBaseEnvironmentProperties(map); + tested.setPooled(false); + tested.afterPropertiesSet(); + Hashtable env = tested.getAnonymousEnv(); + assertEquals("ldap://ldap.example.com:389", env.get(Context.PROVIDER_URL)); + assertNull(env.get(LdapContextSource.SUN_LDAP_POOLING_FLAG)); + } + + public void testGetAnonymousEnvWithEmptyBaseSet() throws Exception { + tested.setUrl("ldap://ldap.example.com:389"); + tested.setBase(null); + tested.afterPropertiesSet(); + Hashtable env = tested.getAnonymousEnv(); + assertEquals("ldap://ldap.example.com:389", env.get(Context.PROVIDER_URL)); + + // check that base was not added to environment + assertNull(env.get(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY)); + } + + public void testOldJdkWithNoBaseSetShouldWork() throws Exception { + tested = new LdapContextSource() { + String getJdkVersion() { + return "1.3"; + } + }; + tested.setUrl("ldap://ldap.example.com:389"); + tested.afterPropertiesSet(); + + // check that base was not added to environment + Hashtable env = tested.getAnonymousEnv(); + assertNull(env.get(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY)); + } + + public void testOldJdkWithBaseSetShouldNotWork() throws Exception { + tested = new LdapContextSource() { + String getJdkVersion() { + return "1.3"; + } + }; + tested.setUrl("ldap://ldap.example.com:389"); + tested.setBase("dc=example,dc=com"); + try { + tested.afterPropertiesSet(); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testOldJdkWithBaseSetToEmptyPathShouldWork() throws Exception { + tested = new LdapContextSource() { + String getJdkVersion() { + return "1.3"; + } + }; + tested.setUrl("ldap://ldap.example.com:389"); + tested.setBase(null); + tested.afterPropertiesSet(); + + // check that base was not added to environment + Hashtable env = tested.getAnonymousEnv(); + assertNull(env.get(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY)); + } + + public void testGetAuthenticatedEnv() throws Exception { + tested.setBase("dc=example,dc=se"); + tested.setUrl("ldap://ldap.example.com:389"); + tested.setPooled(true); + tested.setUserDn("cn=Some User"); + tested.setPassword("secret"); + tested.afterPropertiesSet(); + + Hashtable env = tested.getAuthenticatedEnv(); + assertEquals("ldap://ldap.example.com:389/dc=example,dc=se", env.get(Context.PROVIDER_URL)); + assertEquals("true", env.get(LdapContextSource.SUN_LDAP_POOLING_FLAG)); + assertEquals("cn=Some User", env.get(Context.SECURITY_PRINCIPAL)); + assertEquals("secret", env.get(Context.SECURITY_CREDENTIALS)); + + // check that base was added to environment + assertEquals(new DistinguishedName("dc=example,dc=se"), env.get(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY)); + } + + public void testGetAuthenticatedEnv_DummyAuthenticationProvider() throws Exception { + tested.setBase("dc=example,dc=se"); + tested.setUrl("ldap://ldap.example.com:389"); + tested.setPooled(true); + DummyAuthenticationProvider authenticationProvider = new DummyAuthenticationProvider(); + tested.setAuthenticationSource(authenticationProvider); + authenticationProvider.setPrincipal("cn=Some User"); + authenticationProvider.setCredentials("secret"); + tested.afterPropertiesSet(); + + Hashtable env = tested.getAuthenticatedEnv(); + assertEquals("ldap://ldap.example.com:389/dc=example,dc=se", env.get(Context.PROVIDER_URL)); + assertEquals("true", env.get(LdapContextSource.SUN_LDAP_POOLING_FLAG)); + assertEquals("cn=Some User", env.get(Context.SECURITY_PRINCIPAL)); + assertEquals("secret", env.get(Context.SECURITY_CREDENTIALS)); + } + + public void testGetAuthenticatedEnv_DummyAuthenticationProvider_Changed() throws Exception { + tested.setBase("dc=example,dc=se"); + tested.setUrl("ldap://ldap.example.com:389"); + tested.setPooled(true); + DummyAuthenticationProvider authenticationProvider = new DummyAuthenticationProvider(); + tested.setAuthenticationSource(authenticationProvider); + authenticationProvider.setPrincipal("cn=Some User"); + authenticationProvider.setCredentials("secret"); + tested.afterPropertiesSet(); + + authenticationProvider.setPrincipal("cn=Some Other User"); + authenticationProvider.setCredentials("other secret"); + + Hashtable env = tested.getAuthenticatedEnv(); + assertEquals("cn=Some Other User", env.get(Context.SECURITY_PRINCIPAL)); + assertEquals("other secret", env.get(Context.SECURITY_CREDENTIALS)); + } + + public void testGetAnonymousEnvWhenCacheIsOff() throws Exception { + tested.setBase("dc=example,dc=se"); + tested.setUrl("ldap://ldap.example.com:389"); + tested.setPooled(true); + tested.setUserDn("cn=Some User"); + tested.setPassword("secret"); + tested.setCacheEnvironmentProperties(false); + tested.afterPropertiesSet(); + Hashtable env = tested.getAnonymousEnv(); + assertEquals("ldap://ldap.example.com:389/dc=example,dc=se", env.get(Context.PROVIDER_URL)); + assertEquals("true", env.get(LdapContextSource.SUN_LDAP_POOLING_FLAG)); + assertNull(env.get(Context.SECURITY_PRINCIPAL)); + assertNull(env.get(Context.SECURITY_CREDENTIALS)); + + tested.setUrl("ldap://ldap2.example.com:389"); + env = tested.getAnonymousEnv(); + assertEquals("ldap://ldap2.example.com:389/dc=example,dc=se", env.get(Context.PROVIDER_URL)); + } + + private class DummyAuthenticationProvider implements AuthenticationSource { + private String principal; + + private String credentials; + + public void setCredentials(String credentials) { + this.credentials = credentials; + } + + public void setPrincipal(String principal) { + this.principal = principal; + } + + public String getPrincipal() { + return principal; + } + + public String getCredentials() { + return credentials; + } + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/filter/AbstractFilterTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/filter/AbstractFilterTest.java new file mode 100644 index 00000000..553fe077 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/filter/AbstractFilterTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.springframework.ldap.filter.AbstractFilter; + +import junit.framework.TestCase; + +/** + * @author Adam Skogman + */ +public class AbstractFilterTest extends TestCase { + + /** + * Constructor for AbstractFilterTest. + * + * @param name + */ + public AbstractFilterTest(String name) { + super(name); + } + + /* + * Test for String encode() + */ + public void testEncode() { + + AbstractFilter af = new AbstractFilter() { + public StringBuffer encode(StringBuffer buff) { + return buff.append("foo"); + } + }; + + assertEquals("foo", af.encode()); + + } + + /* + * Test for toString() + */ + public void testToString() { + + AbstractFilter af = new AbstractFilter() { + public StringBuffer encode(StringBuffer buff) { + return buff.append("foo"); + } + }; + + assertEquals("foo", af.toString()); + + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/filter/AndFilterTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/filter/AndFilterTest.java new file mode 100644 index 00000000..55479054 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/filter/AndFilterTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.springframework.ldap.filter.AndFilter; +import org.springframework.ldap.filter.EqualsFilter; + +import com.gargoylesoftware.base.testing.EqualsTester; + +import junit.framework.TestCase; + +/** + * @author Adam Skogman + */ +public class AndFilterTest extends TestCase { + + /** + * Constructor for AndFilterTest. + * + * @param name + */ + public AndFilterTest(String name) { + super(name); + } + + public void testZero() { + AndFilter aq = new AndFilter(); + + assertEquals("", aq.encode()); + } + + public void testOne() { + AndFilter aq = new AndFilter().and(new EqualsFilter("a", "b")); + + assertEquals("(a=b)", aq.encode()); + } + + public void testTwo() { + AndFilter aq = new AndFilter().and(new EqualsFilter("a", "b")).and( + new EqualsFilter("c", "d")); + + assertEquals("(&(a=b)(c=d))", aq.encode()); + } + + public void testThree() { + AndFilter aq = new AndFilter().and(new EqualsFilter("a", "b")).and( + new EqualsFilter("c", "d")).and(new EqualsFilter("e", "f")); + + assertEquals("(&(a=b)(c=d)(e=f))", aq.encode()); + } + + public void testEquals() { + AndFilter originalObject = new AndFilter().and(new EqualsFilter("a", + "b")); + AndFilter identicalObject = new AndFilter().and(new EqualsFilter("a", + "b")); + AndFilter differentObject = new AndFilter().and(new EqualsFilter("b", + "b")); + AndFilter subclassObject = new AndFilter() { + }.and(new EqualsFilter("a", "b")); + + new EqualsTester(originalObject, identicalObject, differentObject, + subclassObject); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/filter/EqualsFilterTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/filter/EqualsFilterTest.java new file mode 100644 index 00000000..e6adbca1 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/filter/EqualsFilterTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.springframework.ldap.filter.EqualsFilter; + +import com.gargoylesoftware.base.testing.EqualsTester; + +import junit.framework.TestCase; + +/** + * @author Adam Skogman + */ +public class EqualsFilterTest extends TestCase { + + /** + * Constructor for EqualsQueryTest. + * + * @param name + */ + public EqualsFilterTest(String name) { + super(name); + } + + public void testEncode() { + + EqualsFilter eqq = new EqualsFilter("foo", "*bar(fie)"); + + StringBuffer buff = new StringBuffer(); + eqq.encode(buff); + + assertEquals("(foo=\\2abar\\28fie\\29)", buff.toString()); + + } + + public void testEncodeInt() { + + EqualsFilter eqq = new EqualsFilter("foo", 456); + + StringBuffer buff = new StringBuffer(); + eqq.encode(buff); + + assertEquals("(foo=456)", buff.toString()); + + } + + public void testEquals() { + EqualsFilter originalObject = new EqualsFilter("a", "b"); + EqualsFilter identicalObject = new EqualsFilter("a", "b"); + EqualsFilter differentObject = new EqualsFilter("b", "b"); + EqualsFilter subclassObject = new EqualsFilter("a", "b") { + }; + + new EqualsTester(originalObject, identicalObject, differentObject, + subclassObject); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/filter/GreaterThanOrEqualsFilterTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/filter/GreaterThanOrEqualsFilterTest.java new file mode 100644 index 00000000..90b639c5 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/filter/GreaterThanOrEqualsFilterTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.springframework.ldap.filter.GreaterThanOrEqualsFilter; + +import junit.framework.TestCase; + +/** + * @author Mattias Arthursson + */ +public class GreaterThanOrEqualsFilterTest extends TestCase { + + /** + * Constructor for EqualsQueryTest. + * + * @param name + */ + public GreaterThanOrEqualsFilterTest(String name) { + super(name); + } + + public void testEncode() { + + GreaterThanOrEqualsFilter eqq = new GreaterThanOrEqualsFilter("foo", + "*bar(fie)"); + + StringBuffer buff = new StringBuffer(); + eqq.encode(buff); + + assertEquals("(foo>=\\2abar\\28fie\\29)", buff.toString()); + + } + + public void testEncodeInt() { + + GreaterThanOrEqualsFilter eqq = new GreaterThanOrEqualsFilter("foo", + 456); + + StringBuffer buff = new StringBuffer(); + eqq.encode(buff); + + assertEquals("(foo>=456)", buff.toString()); + + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/filter/LessThanOrEqualsFilterTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/filter/LessThanOrEqualsFilterTest.java new file mode 100644 index 00000000..a1339b24 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/filter/LessThanOrEqualsFilterTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.springframework.ldap.filter.LessThanOrEqualsFilter; + +import junit.framework.TestCase; + +/** + * @author Mattias Arthursson + */ +public class LessThanOrEqualsFilterTest extends TestCase { + + /** + * Constructor for EqualsQueryTest. + * + * @param name + */ + public LessThanOrEqualsFilterTest(String name) { + super(name); + } + + public void testEncode() { + + LessThanOrEqualsFilter eqq = new LessThanOrEqualsFilter("foo", + "*bar(fie)"); + + StringBuffer buff = new StringBuffer(); + eqq.encode(buff); + + assertEquals("(foo<=\\2abar\\28fie\\29)", buff.toString()); + + } + + public void testEncodeInt() { + + LessThanOrEqualsFilter eqq = new LessThanOrEqualsFilter("foo", 456); + + StringBuffer buff = new StringBuffer(); + eqq.encode(buff); + + assertEquals("(foo<=456)", buff.toString()); + + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/filter/LikeFilterTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/filter/LikeFilterTest.java new file mode 100644 index 00000000..d12996d0 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/filter/LikeFilterTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.springframework.ldap.filter.LikeFilter; + +import junit.framework.TestCase; + +/** + * @author Anders Henja + */ +public class LikeFilterTest extends TestCase { + /** + * Constructor for LikeFilterTest. + * + * @param name + */ + public LikeFilterTest(String name) { + super(name); + } + + final public void testEncodeValue_blank() { + assertEquals("", new LikeFilter("", null).getEncodedValue()); + assertEquals(" ", new LikeFilter("", " ").getEncodedValue()); + } + + final public void testEncodeValue_normal() { + assertEquals("foo", new LikeFilter("", "foo").getEncodedValue()); + assertEquals("foo*bar", new LikeFilter("", "foo*bar").getEncodedValue()); + assertEquals("*foo*bar*", new LikeFilter("", "*foo*bar*") + .getEncodedValue()); + assertEquals("**foo**bar**", new LikeFilter("", "**foo**bar**") + .getEncodedValue()); + } + + final public void testEncodeValue_escape() { + assertEquals("*\\28*\\29*", new LikeFilter("", "*(*)*") + .getEncodedValue()); + assertEquals("*\\5c2a*", new LikeFilter("", "*\\2a*").getEncodedValue()); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/filter/NotFilterTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/filter/NotFilterTest.java new file mode 100644 index 00000000..41888514 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/filter/NotFilterTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.springframework.ldap.filter.EqualsFilter; +import org.springframework.ldap.filter.NotFilter; + +import com.gargoylesoftware.base.testing.EqualsTester; + +import junit.framework.TestCase; + +/** + * @author Mattias Arthursson + */ +public class NotFilterTest extends TestCase { + + public void testConstructor() { + EqualsFilter filter = new EqualsFilter("a", "b"); + NotFilter notFilter = new NotFilter(filter); + + assertEquals("(!(a=b))", notFilter.encode()); + } + + public void testEquals() { + EqualsFilter filter = new EqualsFilter("a", "b"); + NotFilter originalObject = new NotFilter(filter); + NotFilter identicalObject = new NotFilter(filter); + NotFilter differentObject = new NotFilter(new EqualsFilter("a", "a")); + NotFilter subclassObject = new NotFilter(filter) { + }; + + new EqualsTester(originalObject, identicalObject, differentObject, + subclassObject); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/filter/OrFilterTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/filter/OrFilterTest.java new file mode 100644 index 00000000..e5d6337c --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/filter/OrFilterTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.springframework.ldap.filter.EqualsFilter; +import org.springframework.ldap.filter.OrFilter; + +import junit.framework.TestCase; + +/** + * @author Adam Skogman + */ +public class OrFilterTest extends TestCase { + + /** + * Constructor for OrFilterTest. + * + * @param name + */ + public OrFilterTest(String name) { + super(name); + } + + public void testZero() { + OrFilter of = new OrFilter(); + + assertEquals("", of.encode()); + } + + public void testOne() { + OrFilter of = new OrFilter().or(new EqualsFilter("a", "b")); + + assertEquals("(a=b)", of.encode()); + } + + public void testTwo() { + OrFilter of = new OrFilter().or(new EqualsFilter("a", "b")).or( + new EqualsFilter("c", "d")); + + assertEquals("(|(a=b)(c=d))", of.encode()); + } + + public void testThree() { + OrFilter of = new OrFilter().or(new EqualsFilter("a", "b")).or( + new EqualsFilter("c", "d")).or(new EqualsFilter("e", "f")); + + assertEquals("(|(a=b)(c=d)(e=f))", of.encode()); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/filter/WhitespaceWildcardsFilterTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/filter/WhitespaceWildcardsFilterTest.java new file mode 100644 index 00000000..b0094daf --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/filter/WhitespaceWildcardsFilterTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.filter; + +import org.springframework.ldap.filter.WhitespaceWildcardsFilter; + +import junit.framework.TestCase; + +/** + * @author Adam Skogman + */ +public class WhitespaceWildcardsFilterTest extends TestCase { + + /** + * Constructor for WhitespaceWildcardsFilterTest. + * + * @param name + */ + public WhitespaceWildcardsFilterTest(String name) { + super(name); + } + + final public void testEncodeValue_blank() { + + // blank + assertEquals("*", new WhitespaceWildcardsFilter("", null) + .getEncodedValue()); + assertEquals("*", new WhitespaceWildcardsFilter("", " ") + .getEncodedValue()); + assertEquals("*", new WhitespaceWildcardsFilter("", " ") + .getEncodedValue()); + assertEquals("*", new WhitespaceWildcardsFilter("", "\t") + .getEncodedValue()); + + } + + final public void testEncodeValue_normal() { + + assertEquals("*foo*", new WhitespaceWildcardsFilter("", "foo") + .getEncodedValue()); + assertEquals("*foo*bar*", new WhitespaceWildcardsFilter("", "foo bar") + .getEncodedValue()); + assertEquals("*foo*bar*", + new WhitespaceWildcardsFilter("", " foo bar ") + .getEncodedValue()); + assertEquals("*foo*bar*", new WhitespaceWildcardsFilter("", + " \t foo \n bar \r ").getEncodedValue()); + } + + final public void testEncodeValue_escape() { + + assertEquals("*\\28\\2a\\29*", new WhitespaceWildcardsFilter("", "(*)") + .getEncodedValue()); + assertEquals("*\\2a*", new WhitespaceWildcardsFilter("", "*") + .getEncodedValue()); + assertEquals("*\\5c*", new WhitespaceWildcardsFilter("", " \\ ") + .getEncodedValue()); + + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/pool/AbstractPoolTestCase.java b/mvn-build/core/src/test/java/org/springframework/ldap/pool/AbstractPoolTestCase.java new file mode 100644 index 00000000..b5994969 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/pool/AbstractPoolTestCase.java @@ -0,0 +1,123 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool; + +import javax.naming.Context; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import org.apache.commons.pool.KeyedObjectPool; +import org.easymock.MockControl; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.pool.validation.DirContextValidator; + +import junit.framework.TestCase; + +/** + * Contains mocks common to many tests for the connection pool. + * + * @author Ulrik Sandberg + */ +public abstract class AbstractPoolTestCase extends TestCase { + + protected MockControl contextControl; + + protected Context contextMock; + + protected MockControl dirContextControl; + + protected DirContext dirContextMock; + + protected MockControl ldapContextControl; + + protected LdapContext ldapContextMock; + + protected MockControl keyedObjectPoolControl; + + protected KeyedObjectPool keyedObjectPoolMock; + + protected MockControl contextSourceControl; + + protected ContextSource contextSourceMock; + + protected MockControl dirContextValidatorControl; + + protected DirContextValidator dirContextValidatorMock; + + protected void setUp() throws Exception { + super.setUp(); + + contextControl = MockControl.createControl(Context.class); + contextMock = (Context) contextControl.getMock(); + + dirContextControl = MockControl.createControl(DirContext.class); + dirContextMock = (DirContext) dirContextControl.getMock(); + + ldapContextControl = MockControl.createControl(LdapContext.class); + ldapContextMock = (LdapContext) ldapContextControl.getMock(); + + keyedObjectPoolControl = MockControl + .createControl(KeyedObjectPool.class); + keyedObjectPoolMock = (KeyedObjectPool) keyedObjectPoolControl + .getMock(); + + contextSourceControl = MockControl.createControl(ContextSource.class); + contextSourceMock = (ContextSource) contextSourceControl.getMock(); + + dirContextValidatorControl = MockControl.createControl(DirContextValidator.class); + dirContextValidatorMock = (DirContextValidator) dirContextValidatorControl.getMock(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + contextControl = null; + contextMock = null; + + dirContextControl = null; + dirContextMock = null; + + ldapContextControl = null; + ldapContextMock = null; + + keyedObjectPoolControl = null; + keyedObjectPoolMock = null; + + contextSourceControl = null; + contextSourceMock = null; + + dirContextValidatorControl = null; + dirContextValidatorMock = null; + } + + protected void replay() { + contextControl.replay(); + dirContextControl.replay(); + ldapContextControl.replay(); + keyedObjectPoolControl.replay(); + contextSourceControl.replay(); + dirContextValidatorControl.replay(); + } + + protected void verify() { + contextControl.verify(); + dirContextControl.verify(); + ldapContextControl.verify(); + keyedObjectPoolControl.verify(); + contextSourceControl.verify(); + dirContextValidatorControl.verify(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingContextTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingContextTest.java new file mode 100644 index 00000000..b8ce60cb --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingContextTest.java @@ -0,0 +1,446 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingException; + +import org.apache.commons.pool.KeyedObjectPool; +import org.easymock.MockControl; + +/** + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + */ +public class DelegatingContextTest extends AbstractPoolTestCase { + + public void testConstructorAssertions() { + + replay(); + + try { + new DelegatingContext(null, contextMock, DirContextType.READ_ONLY); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new DelegatingContext(keyedObjectPoolMock, null, + DirContextType.READ_ONLY); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new DelegatingContext(keyedObjectPoolMock, contextMock, null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + verify(); + } + + public void testHelperMethods() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, contextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + // Wrap the Context once + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + final Context delegateContext = delegatingContext.getDelegateContext(); + assertEquals(contextMock, delegateContext); + + final Context innerDelegateContext = delegatingContext + .getInnermostDelegateContext(); + assertEquals(contextMock, innerDelegateContext); + + delegatingContext.assertOpen(); + + // Wrap the wrapper + MockControl secondKeyedObjectPoolControl = MockControl + .createControl(KeyedObjectPool.class); + KeyedObjectPool secondKeyedObjectPoolMock = (KeyedObjectPool) secondKeyedObjectPoolControl + .getMock(); + secondKeyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + delegatingContext); + secondKeyedObjectPoolControl.setVoidCallable(1); + secondKeyedObjectPoolControl.replay(); + + final DelegatingContext delegatingContext2 = new DelegatingContext( + secondKeyedObjectPoolMock, delegatingContext, + DirContextType.READ_ONLY); + + final Context delegateContext2 = delegatingContext2 + .getDelegateContext(); + assertEquals(delegatingContext, delegateContext2); + + final Context innerDelegateContext2 = delegatingContext2 + .getInnermostDelegateContext(); + assertEquals(contextMock, innerDelegateContext2); + + delegatingContext2.assertOpen(); + + // Close the outer wrapper + delegatingContext2.close(); + + final Context delegateContext2closed = delegatingContext2 + .getDelegateContext(); + assertNull(delegateContext2closed); + + final Context innerDelegateContext2closed = delegatingContext2 + .getInnermostDelegateContext(); + assertNull(innerDelegateContext2closed); + + try { + delegatingContext2.assertOpen(); + fail("delegatingContext2.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + // Close the outer wrapper + delegatingContext.close(); + + final Context delegateContextclosed = delegatingContext + .getDelegateContext(); + assertNull(delegateContextclosed); + + final Context innerDelegateContextclosed = delegatingContext + .getInnermostDelegateContext(); + assertNull(innerDelegateContextclosed); + + try { + delegatingContext.assertOpen(); + fail("delegatingContext.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + verify(); + secondKeyedObjectPoolControl.verify(); + } + + public void testObjectMethods() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, contextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + // Wrap the Context once + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + assertEquals("EasyMock for interface javax.naming.Context", + delegatingContext.toString()); + delegatingContext.hashCode(); // Run it to make sure it doesn't fail + + assertTrue(delegatingContext.equals(delegatingContext)); + assertFalse(delegatingContext.equals(new Object())); + + final DelegatingContext delegatingContext2 = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + assertTrue(delegatingContext.equals(delegatingContext2)); + assertTrue(delegatingContext2.equals(delegatingContext)); + assertTrue(delegatingContext.equals(contextMock)); + + // Close the contextMock and try again + delegatingContext.close(); + + assertEquals("Context is closed", delegatingContext.toString()); + assertEquals(0, delegatingContext.hashCode()); // Run it to make sure + // it doesn't fail + + assertTrue(delegatingContext.equals(delegatingContext)); + assertFalse(delegatingContext.equals(new Object())); + + assertFalse(delegatingContext.equals(delegatingContext2)); + assertFalse(delegatingContext2.equals(delegatingContext)); + assertFalse(delegatingContext.equals(contextMock)); + + verify(); + } + + public void testUnsupportedMethods() throws Exception { + + replay(); + + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + try { + delegatingContext.addToEnvironment(null, null); + fail("DelegatingContext.addToEnvironment Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingContext.createSubcontext((Name) null); + fail("DelegatingContext.createSubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingContext.createSubcontext((String) null); + fail("DelegatingContext.createSubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingContext.destroySubcontext((Name) null); + fail("DelegatingContext.destroySubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingContext.destroySubcontext((String) null); + fail("DelegatingContext.destroySubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingContext.removeFromEnvironment(null); + fail("DelegatingContext.removeFromEnvironment Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + verify(); + } + + public void testAllMethodsOpened() throws Exception { + contextControl = MockControl.createNiceControl(Context.class); + contextMock = (Context) contextControl.getMock(); + + replay(); + + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + delegatingContext.bind((Name) null, null); + delegatingContext.bind((String) null, null); + delegatingContext.composeName((Name) null, (Name) null); + delegatingContext.composeName((String) null, (String) null); + delegatingContext.getEnvironment(); + delegatingContext.getNameInNamespace(); + delegatingContext.getNameParser((Name) null); + delegatingContext.getNameParser((String) null); + delegatingContext.list((Name) null); + delegatingContext.list((String) null); + delegatingContext.listBindings((Name) null); + delegatingContext.listBindings((String) null); + delegatingContext.lookup((Name) null); + delegatingContext.lookup((String) null); + delegatingContext.lookupLink((Name) null); + delegatingContext.lookupLink((String) null); + delegatingContext.rebind((Name) null, null); + delegatingContext.rebind((String) null, null); + delegatingContext.rename((Name) null, (Name) null); + delegatingContext.rename((String) null, (String) null); + delegatingContext.unbind((Name) null); + delegatingContext.unbind((String) null); + + verify(); + } + + public void testAllMethodsClosed() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, contextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + delegatingContext.close(); + + try { + delegatingContext.bind((Name) null, null); + fail("DelegatingContext.bind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.bind((String) null, null); + fail("DelegatingContext.bind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.composeName((Name) null, (Name) null); + fail("DelegatingContext.composeName should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.composeName((String) null, (String) null); + fail("DelegatingContext.composeName should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.getEnvironment(); + fail("DelegatingContext.getEnvironment should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.getNameInNamespace(); + fail("DelegatingContext.getNameInNamespace should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.getNameParser((Name) null); + fail("DelegatingContext.getNameParser should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.getNameParser((String) null); + fail("DelegatingContext.getNameParser should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.list((Name) null); + fail("DelegatingContext.list should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.list((String) null); + fail("DelegatingContext.list should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.listBindings((Name) null); + fail("DelegatingContext.listBindings should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.listBindings((String) null); + fail("DelegatingContext.listBindings should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.lookup((Name) null); + fail("DelegatingContext.lookup should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.lookup((String) null); + fail("DelegatingContext.lookup should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.lookupLink((Name) null); + fail("DelegatingContext.lookupLink should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.lookupLink((String) null); + fail("DelegatingContext.lookupLink should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.rebind((Name) null, null); + fail("DelegatingContext.rebind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.rebind((String) null, null); + fail("DelegatingContext.rebind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.rename((Name) null, (Name) null); + fail("DelegatingContext.rename should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.rename((String) null, (String) null); + fail("DelegatingContext.rename should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.unbind((Name) null); + fail("DelegatingContext.unbind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.unbind((String) null); + fail("DelegatingContext.unbind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + verify(); + } + + public void testDoubleClose() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, contextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + delegatingContext.close(); + + // noop close + delegatingContext.close(); + + verify(); + } + + public void testPoolExceptionOnClose() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, contextMock); + keyedObjectPoolControl.setThrowable(new Exception( + "Fake Pool returnObject Exception")); + + replay(); + + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + try { + delegatingContext.close(); + fail("DelegatingContext.close should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + verify(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingDirContextTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingDirContextTest.java new file mode 100644 index 00000000..be39901c --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingDirContextTest.java @@ -0,0 +1,419 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; + +import org.apache.commons.pool.KeyedObjectPool; +import org.easymock.MockControl; + +/** + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + */ +public class DelegatingDirContextTest extends AbstractPoolTestCase { + public void testConstructorAssertions() { + replay(); + + try { + new DelegatingDirContext(keyedObjectPoolMock, null, + DirContextType.READ_ONLY); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new DelegatingDirContext(keyedObjectPoolMock, dirContextMock, null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + verify(); + } + + public void testHelperMethods() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + dirContextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + // Wrap the DirContext once + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + + final Context delegateContext = delegatingDirContext + .getDelegateContext(); + assertEquals(dirContextMock, delegateContext); + + final DirContext delegateDirContext = delegatingDirContext + .getDelegateDirContext(); + assertEquals(dirContextMock, delegateDirContext); + + final DirContext innerDelegateDirContext = delegatingDirContext + .getInnermostDelegateDirContext(); + assertEquals(dirContextMock, innerDelegateDirContext); + + delegatingDirContext.assertOpen(); + + // Wrap the wrapper + MockControl secondKeyedObjectPoolControl = MockControl + .createControl(KeyedObjectPool.class); + KeyedObjectPool secondKeyedObjectPoolMock = (KeyedObjectPool) secondKeyedObjectPoolControl + .getMock(); + secondKeyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + delegatingDirContext); + secondKeyedObjectPoolControl.setVoidCallable(1); + secondKeyedObjectPoolControl.replay(); + + final DelegatingDirContext delegatingDirContext2 = new DelegatingDirContext( + secondKeyedObjectPoolMock, delegatingDirContext, + DirContextType.READ_ONLY); + + final DirContext delegateDirContext2 = delegatingDirContext2 + .getDelegateDirContext(); + assertEquals(delegatingDirContext, delegateDirContext2); + + final DirContext innerDelegateDirContext2 = delegatingDirContext2 + .getInnermostDelegateDirContext(); + assertEquals(dirContextMock, innerDelegateDirContext2); + + delegatingDirContext2.assertOpen(); + + // Close the outer wrapper + delegatingDirContext2.close(); + + final DirContext delegateContext2closed = delegatingDirContext2 + .getDelegateDirContext(); + assertNull(delegateContext2closed); + + final DirContext innerDelegateContext2closed = delegatingDirContext2 + .getInnermostDelegateDirContext(); + assertNull(innerDelegateContext2closed); + + try { + delegatingDirContext2.assertOpen(); + fail("delegatingDirContext2.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + // Close the outer wrapper + delegatingDirContext.close(); + + final DirContext delegateDirContextClosed = delegatingDirContext + .getDelegateDirContext(); + assertNull(delegateDirContextClosed); + + final DirContext innerDelegateDirContextClosed = delegatingDirContext + .getInnermostDelegateDirContext(); + assertNull(innerDelegateDirContextClosed); + + try { + delegatingDirContext.assertOpen(); + fail("delegatingDirContext.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + secondKeyedObjectPoolControl.verify(); + verify(); + } + + public void testObjectMethods() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + dirContextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + // Wrap the DirContext once + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + assertEquals( + "EasyMock for interface javax.naming.directory.DirContext", + delegatingDirContext.toString()); + delegatingDirContext.hashCode(); // Run it to make sure it doesn't + // fail + + assertTrue(delegatingDirContext.equals(delegatingDirContext)); + assertFalse(delegatingDirContext.equals(new Object())); + + final DelegatingDirContext delegatingDirContext2 = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + assertTrue(delegatingDirContext.equals(delegatingDirContext2)); + assertTrue(delegatingDirContext2.equals(delegatingDirContext)); + assertTrue(delegatingDirContext.equals(dirContextMock)); + + // Close the context and try again + delegatingDirContext.close(); + + assertEquals("DirContext is closed", delegatingDirContext.toString()); + assertEquals(0, delegatingDirContext.hashCode()); // Run it to make + // sure it doesn't + // fail + + assertTrue(delegatingDirContext.equals(delegatingDirContext)); + assertFalse(delegatingDirContext.equals(new Object())); + + assertFalse(delegatingDirContext.equals(delegatingDirContext2)); + assertFalse(delegatingDirContext2.equals(delegatingDirContext)); + assertFalse(delegatingDirContext.equals(dirContextMock)); + + verify(); + } + + // nice + public void testUnsupportedMethods() throws Exception { + replay(); + + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + + try { + delegatingDirContext.createSubcontext((Name) null, null); + fail("DelegatingDirContext.createSubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingDirContext.createSubcontext((String) null, null); + fail("DelegatingDirContext.createSubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingDirContext.getSchema((Name) null); + fail("DelegatingDirContext.getSchema Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingDirContext.getSchema((String) null); + fail("DelegatingDirContext.getSchema Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingDirContext.getSchemaClassDefinition((Name) null); + fail("DelegatingDirContext.getSchemaClassDefinition Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingDirContext.getSchemaClassDefinition((String) null); + fail("DelegatingDirContext.getSchemaClassDefinition Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + + verify(); + } + + public void testAllMethodsOpened() throws Exception { + // override with a nice control + dirContextControl = MockControl.createNiceControl(DirContext.class); + dirContextMock = (DirContext) dirContextControl.getMock(); + + replay(); + + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + + delegatingDirContext.bind((Name) null, null, null); + delegatingDirContext.bind((String) null, null, null); + delegatingDirContext.getAttributes((Name) null, null); + delegatingDirContext.getAttributes((Name) null); + delegatingDirContext.getAttributes((String) null, null); + delegatingDirContext.getAttributes((String) null); + delegatingDirContext.modifyAttributes((Name) null, 0, null); + delegatingDirContext.modifyAttributes((Name) null, null); + delegatingDirContext.modifyAttributes((String) null, 0, null); + delegatingDirContext.modifyAttributes((String) null, null); + delegatingDirContext.rebind((Name) null, null, null); + delegatingDirContext.rebind((String) null, null, null); + delegatingDirContext.search((Name) null, (Attributes) null, null); + delegatingDirContext.search((Name) null, null); + delegatingDirContext.search((Name) null, null, null, null); + delegatingDirContext.search((Name) null, (String) null, null); + delegatingDirContext.search((String) null, (Attributes) null, null); + delegatingDirContext.search((String) null, null); + delegatingDirContext.search((String) null, null, null, null); + delegatingDirContext.search((String) null, (String) null, null); + + verify(); + } + + public void testAllMethodsClosed() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + dirContextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + + delegatingDirContext.close(); + + try { + delegatingDirContext.bind((Name) null, null, null); + fail("DelegatingDirContext.bind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.bind((String) null, null, null); + fail("DelegatingDirContext.bind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.getAttributes((Name) null, null); + fail("DelegatingDirContext.getAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.getAttributes((Name) null); + fail("DelegatingDirContext.getAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.getAttributes((String) null, null); + fail("DelegatingDirContext.getAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.getAttributes((String) null); + fail("DelegatingDirContext.getAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.modifyAttributes((Name) null, 0, null); + fail("DelegatingDirContext.modifyAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.modifyAttributes((Name) null, null); + fail("DelegatingDirContext.modifyAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.modifyAttributes((String) null, 0, null); + fail("DelegatingDirContext.modifyAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.modifyAttributes((String) null, null); + fail("DelegatingDirContext.modifyAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.rebind((Name) null, null, null); + fail("DelegatingDirContext.rebind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.rebind((String) null, null, null); + fail("DelegatingDirContext.rebind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((Name) null, (Attributes) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((Name) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((Name) null, null, null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((Name) null, (String) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((String) null, (Attributes) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((String) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((String) null, null, null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((String) null, (String) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + verify(); + } + + public void testDoubleClose() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + dirContextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + + delegatingDirContext.close(); + + // noop close + delegatingDirContext.close(); + + verify(); + } +} \ No newline at end of file diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingLdapContextTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingLdapContextTest.java new file mode 100644 index 00000000..46023958 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/pool/DelegatingLdapContextTest.java @@ -0,0 +1,287 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import org.apache.commons.pool.KeyedObjectPool; +import org.easymock.MockControl; + +/** + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + */ +public class DelegatingLdapContextTest extends AbstractPoolTestCase { + public void testConstructorAssertions() { + replay(); + + try { + new DelegatingLdapContext(keyedObjectPoolMock, null, + DirContextType.READ_ONLY); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new DelegatingLdapContext(keyedObjectPoolMock, ldapContextMock, + null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + verify(); + } + + public void testHelperMethods() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + ldapContextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + // Wrap the LdapContext once + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + final DirContext delegateDirContext = delegatingLdapContext + .getDelegateDirContext(); + assertEquals(ldapContextMock, delegateDirContext); + + final LdapContext delegateLdapContext = delegatingLdapContext + .getDelegateLdapContext(); + assertEquals(ldapContextMock, delegateLdapContext); + + final LdapContext innerDelegateLdapContext = delegatingLdapContext + .getInnermostDelegateLdapContext(); + assertEquals(ldapContextMock, innerDelegateLdapContext); + + delegatingLdapContext.assertOpen(); + + // Wrap the wrapper + MockControl secondKeyedObjectPoolControl = MockControl + .createControl(KeyedObjectPool.class); + KeyedObjectPool secondKeyedObjectPoolMock = (KeyedObjectPool) secondKeyedObjectPoolControl + .getMock(); + + secondKeyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + delegatingLdapContext); + secondKeyedObjectPoolControl.setVoidCallable(1); + secondKeyedObjectPoolControl.replay(); + + final DelegatingLdapContext delegatingLdapContext2 = new DelegatingLdapContext( + secondKeyedObjectPoolMock, delegatingLdapContext, + DirContextType.READ_ONLY); + + final LdapContext delegateLdapContext2 = delegatingLdapContext2 + .getDelegateLdapContext(); + assertEquals(delegatingLdapContext, delegateLdapContext2); + + final LdapContext innerDelegateLdapContext2 = delegatingLdapContext2 + .getInnermostDelegateLdapContext(); + assertEquals(ldapContextMock, innerDelegateLdapContext2); + + delegatingLdapContext2.assertOpen(); + + // Close the outer wrapper + delegatingLdapContext2.close(); + + final LdapContext delegateContext2closed = delegatingLdapContext2 + .getDelegateLdapContext(); + assertNull(delegateContext2closed); + + final LdapContext innerDelegateContext2closed = delegatingLdapContext2 + .getInnermostDelegateLdapContext(); + assertNull(innerDelegateContext2closed); + + try { + delegatingLdapContext2.assertOpen(); + fail("delegatingLdapContext2.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + // Close the outer wrapper + delegatingLdapContext.close(); + + final LdapContext delegateLdapContextClosed = delegatingLdapContext + .getDelegateLdapContext(); + assertNull(delegateLdapContextClosed); + + final LdapContext innerDelegateLdapContextClosed = delegatingLdapContext + .getInnermostDelegateLdapContext(); + assertNull(innerDelegateLdapContextClosed); + + try { + delegatingLdapContext.assertOpen(); + fail("delegatingLdapContext.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + secondKeyedObjectPoolControl.verify(); + verify(); + } + + public void testObjectMethods() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + ldapContextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + // Wrap the LdapContext once + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + assertEquals("EasyMock for interface javax.naming.ldap.LdapContext", + delegatingLdapContext.toString()); + delegatingLdapContext.hashCode(); // Run it to make sure it doesn't fail + + assertTrue(delegatingLdapContext.equals(delegatingLdapContext)); + assertFalse(delegatingLdapContext.equals(new Object())); + + final DelegatingLdapContext delegatingLdapContext2 = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + assertTrue(delegatingLdapContext.equals(delegatingLdapContext2)); + assertTrue(delegatingLdapContext2.equals(delegatingLdapContext)); + assertTrue(delegatingLdapContext.equals(ldapContextMock)); + + // Close the context and try again + delegatingLdapContext.close(); + + assertEquals("LdapContext is closed", delegatingLdapContext.toString()); + assertEquals(0, delegatingLdapContext.hashCode()); // Run it to make + // sure it doesn't + // fail + + assertTrue(delegatingLdapContext.equals(delegatingLdapContext)); + assertFalse(delegatingLdapContext.equals(new Object())); + + assertFalse(delegatingLdapContext.equals(delegatingLdapContext2)); + assertFalse(delegatingLdapContext2.equals(delegatingLdapContext)); + assertFalse(delegatingLdapContext.equals(ldapContextMock)); + + verify(); + } + + public void testUnsupportedMethods() throws Exception { + replay(); + + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + try { + delegatingLdapContext.newInstance(null); + fail("DelegatingLdapContext.newInstance Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingLdapContext.reconnect(null); + fail("DelegatingLdapContext.reconnect Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingLdapContext.setRequestControls(null); + fail("DelegatingLdapContext.setRequestControls Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + + verify(); + } + + // nice + public void testAllMethodsOpened() throws Exception { + MockControl ldapContextControl = MockControl.createNiceControl(LdapContext.class); + LdapContext ldapContextMock = (LdapContext) ldapContextControl.getMock(); + + ldapContextControl.replay(); + replay(); + + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + delegatingLdapContext.extendedOperation(null); + delegatingLdapContext.getConnectControls(); + delegatingLdapContext.getRequestControls(); + delegatingLdapContext.getResponseControls(); + + ldapContextControl.verify(); + verify(); + } + + public void testAllMethodsClosed() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + ldapContextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + delegatingLdapContext.close(); + + try { + delegatingLdapContext.extendedOperation(null); + fail("DelegatingLdapContext.extendedOperation should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingLdapContext.getConnectControls(); + fail("DelegatingLdapContext.getConnectControls should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingLdapContext.getRequestControls(); + fail("DelegatingLdapContext.getRequestControls should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingLdapContext.getResponseControls(); + fail("DelegatingLdapContext.getResponseControls should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + verify(); + } + + public void testDoubleClose() throws Exception { + keyedObjectPoolMock.returnObject(DirContextType.READ_ONLY, + ldapContextMock); + keyedObjectPoolControl.setVoidCallable(1); + + replay(); + + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + delegatingLdapContext.close(); + + // noop close + delegatingLdapContext.close(); + + verify(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/pool/factory/DirContextPoolableObjectFactoryTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/pool/factory/DirContextPoolableObjectFactoryTest.java new file mode 100644 index 00000000..fe267467 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/pool/factory/DirContextPoolableObjectFactoryTest.java @@ -0,0 +1,243 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool.factory; + +import javax.naming.directory.DirContext; + +import org.easymock.MockControl; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.pool.AbstractPoolTestCase; +import org.springframework.ldap.pool.DirContextType; +import org.springframework.ldap.pool.validation.DirContextValidator; + +/** + * @author Eric Dalquist + */ +public class DirContextPoolableObjectFactoryTest extends AbstractPoolTestCase { + + public void testProperties() throws Exception { + final DirContextPoolableObjectFactory objectFactory = new DirContextPoolableObjectFactory(); + + replay(); + + try { + objectFactory.setContextSource(null); + fail("DirContextPoolableObjectFactory.setContextSource should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException iae) { + // Expected + } + + objectFactory.setContextSource(contextSourceMock); + final ContextSource contextSource2 = objectFactory.getContextSource(); + assertEquals(contextSourceMock, contextSource2); + + + try { + objectFactory.setDirContextValidator(null); + fail("DirContextPoolableObjectFactory.setDirContextValidator should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException iae) { + // Expected + } + + objectFactory.setDirContextValidator(dirContextValidatorMock); + final DirContextValidator dirContextValidator2 = objectFactory.getDirContextValidator(); + assertEquals(dirContextValidatorMock, dirContextValidator2); + verify(); + } + + public void testMakeObjectAssertions() throws Exception { + final DirContextPoolableObjectFactory objectFactory = new DirContextPoolableObjectFactory(); + + replay(); + + try { + objectFactory.makeObject(DirContextType.READ_ONLY); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + objectFactory.setContextSource(contextSourceMock); + + try { + objectFactory.makeObject(null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + verify(); + } + + public void testMakeObjectReadOnly() throws Exception { + final DirContextPoolableObjectFactory objectFactory = new DirContextPoolableObjectFactory(); + + MockControl readOnlyContextControl = MockControl.createControl(DirContext.class); + DirContext readOnlyContextMock = (DirContext) readOnlyContextControl.getMock(); + + readOnlyContextControl.replay(); + + contextSourceControl.expectAndReturn(contextSourceMock.getReadOnlyContext(), readOnlyContextMock, 1); + objectFactory.setContextSource(contextSourceMock); + + replay(); + + final Object createdDirContext = objectFactory.makeObject(DirContextType.READ_ONLY); + + readOnlyContextControl.verify(); + verify(); + assertEquals(readOnlyContextMock, createdDirContext); + } + + public void testMakeObjectReadWrite() throws Exception { + final DirContextPoolableObjectFactory objectFactory = new DirContextPoolableObjectFactory(); + + MockControl readWriteContextControl = MockControl.createControl(DirContext.class); + DirContext readWriteContextMock = (DirContext) readWriteContextControl.getMock(); + + readWriteContextControl.replay(); + + contextSourceControl.expectAndReturn(contextSourceMock.getReadWriteContext(), readWriteContextMock, 1); + objectFactory.setContextSource(contextSourceMock); + + replay(); + + final Object createdDirContext = objectFactory.makeObject(DirContextType.READ_WRITE); + + readWriteContextControl.verify(); + verify(); + assertEquals(readWriteContextMock, createdDirContext); + } + + public void testValidateObjectAssertions() throws Exception { + final DirContextPoolableObjectFactory objectFactory = new DirContextPoolableObjectFactory(); + + replay(); + + try { + objectFactory.validateObject(DirContextType.READ_ONLY, dirContextMock); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + objectFactory.setDirContextValidator(dirContextValidatorMock); + + try { + objectFactory.validateObject(null, dirContextMock); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + objectFactory.validateObject(new Object(), dirContextMock); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + objectFactory.validateObject(DirContextType.READ_ONLY, null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + objectFactory.validateObject(DirContextType.READ_ONLY, new Object()); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + verify(); + } + + public void testValidateObject() throws Exception { + dirContextValidatorControl.expectAndReturn(dirContextValidatorMock + .validateDirContext(DirContextType.READ_ONLY, dirContextMock), + true); + + replay(); + + final DirContextPoolableObjectFactory objectFactory = new DirContextPoolableObjectFactory(); + objectFactory.setDirContextValidator(dirContextValidatorMock); + + final boolean valid = objectFactory.validateObject(DirContextType.READ_ONLY, dirContextMock); + assertTrue(valid); + + //Check exception in validator + MockControl secondDirContextValidatorControl = MockControl.createControl(DirContextValidator.class); + DirContextValidator secondDirContextValidatorMock = (DirContextValidator) secondDirContextValidatorControl.getMock(); + + secondDirContextValidatorControl.expectAndThrow(secondDirContextValidatorMock.validateDirContext(DirContextType.READ_ONLY, dirContextMock), new RuntimeException("Failed to validate")); + secondDirContextValidatorControl.replay(); + objectFactory.setDirContextValidator(secondDirContextValidatorMock); + + final boolean valid2 = objectFactory.validateObject(DirContextType.READ_ONLY, dirContextMock); + assertFalse(valid2); + + secondDirContextValidatorControl.verify(); + verify(); + } + + public void testDestroyObjectAssertions() throws Exception { + final DirContextPoolableObjectFactory objectFactory = new DirContextPoolableObjectFactory(); + + replay(); + + try { + objectFactory.destroyObject(DirContextType.READ_ONLY, null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + objectFactory.validateObject(DirContextType.READ_ONLY, new Object()); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + verify(); + } + + public void testDestroyObject() throws Exception { + dirContextMock.close(); + dirContextControl.setVoidCallable(1); + + replay(); + + final DirContextPoolableObjectFactory objectFactory = new DirContextPoolableObjectFactory(); + + objectFactory.destroyObject(DirContextType.READ_ONLY, dirContextMock); + + MockControl throwingDirContextControl = MockControl.createControl(DirContext.class); + DirContext throwingDirContextMock = (DirContext) throwingDirContextControl.getMock(); + + throwingDirContextMock.close(); + throwingDirContextControl.setThrowable(new RuntimeException("Failed to close")); + throwingDirContextControl.replay(); + + objectFactory.destroyObject(DirContextType.READ_ONLY, throwingDirContextMock); + throwingDirContextControl.verify(); + verify(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/pool/factory/PoolingContextSourceTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/pool/factory/PoolingContextSourceTest.java new file mode 100644 index 00000000..00b8dc2d --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/pool/factory/PoolingContextSourceTest.java @@ -0,0 +1,273 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool.factory; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import org.apache.commons.pool.impl.GenericKeyedObjectPool; +import org.easymock.MockControl; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.pool.AbstractPoolTestCase; +import org.springframework.ldap.pool.validation.DirContextValidator; + +/** + * @author Eric Dalquist + */ +public class PoolingContextSourceTest extends AbstractPoolTestCase { + + public void testProperties() throws Exception { + final PoolingContextSource poolingContextSource = new PoolingContextSource(); + + replay(); + + try { + poolingContextSource.setContextSource(null); + fail("PoolingContextSource.setBaseName should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException iae) { + // Expected + } + poolingContextSource.setContextSource(contextSourceMock); + final ContextSource contextSource2 = poolingContextSource.getContextSource(); + assertEquals(contextSourceMock, contextSource2); + + try { + poolingContextSource.setDirContextValidator(null); + fail("PoolingContextSource.setDirContextValidator should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException iae) { + // Expected + } + poolingContextSource.setDirContextValidator(dirContextValidatorMock); + final DirContextValidator dirContextValidator2 = poolingContextSource.getDirContextValidator(); + assertEquals(dirContextValidatorMock, dirContextValidator2); + + poolingContextSource.setMaxActive(1000); + final int maxActive = poolingContextSource.getMaxActive(); + assertEquals(1000, maxActive); + + poolingContextSource.setMaxIdle(500); + final int maxIdle = poolingContextSource.getMaxIdle(); + assertEquals(500, maxIdle); + + poolingContextSource.setMaxTotal(5000); + final int maxTotal = poolingContextSource.getMaxTotal(); + assertEquals(5000, maxTotal); + + poolingContextSource.setMaxWait(2000L); + final long maxWait = poolingContextSource.getMaxWait(); + assertEquals(2000L, maxWait); + + poolingContextSource.setMinEvictableIdleTimeMillis(60000L); + final long minEvictableIdleTimeMillis = poolingContextSource.getMinEvictableIdleTimeMillis(); + assertEquals(60000L, minEvictableIdleTimeMillis); + + poolingContextSource.setMinIdle(100); + final int minIdle = poolingContextSource.getMinIdle(); + assertEquals(100, minIdle); + + poolingContextSource.setNumTestsPerEvictionRun(5); + final int numTestsPerEvictionRun = poolingContextSource.getNumTestsPerEvictionRun(); + assertEquals(5, numTestsPerEvictionRun); + + poolingContextSource.setTestOnBorrow(true); + final boolean testOnBorrow = poolingContextSource.getTestOnBorrow(); + assertEquals(true, testOnBorrow); + + poolingContextSource.setTestOnReturn(true); + final boolean testOnReturn = poolingContextSource.getTestOnReturn(); + assertEquals(true, testOnReturn); + + poolingContextSource.setTestWhileIdle(true); + final boolean testWhileIdle = poolingContextSource.getTestWhileIdle(); + assertEquals(true, testWhileIdle); + + poolingContextSource.setTimeBetweenEvictionRunsMillis(120000L); + final long timeBetweenEvictionRunsMillis = poolingContextSource.getTimeBetweenEvictionRunsMillis(); + assertEquals(120000L, timeBetweenEvictionRunsMillis); + + poolingContextSource.setWhenExhaustedAction(GenericKeyedObjectPool.WHEN_EXHAUSTED_BLOCK); + final byte whenExhaustedAction = poolingContextSource.getWhenExhaustedAction(); + assertEquals(GenericKeyedObjectPool.WHEN_EXHAUSTED_BLOCK, whenExhaustedAction); + + final int numActive = poolingContextSource.getNumActive(); + assertEquals(0, numActive); + + final int numIdle = poolingContextSource.getNumIdle(); + assertEquals(0, numIdle); + } + + public void testGetReadOnlyContextPool() throws Exception { + MockControl secondDirContextControl = MockControl.createControl(DirContext.class); + DirContext secondDirContextMock = (DirContext) secondDirContextControl.getMock(); + + contextSourceControl.expectAndReturn(contextSourceMock.getReadOnlyContext(), dirContextMock); + contextSourceControl.expectAndReturn(contextSourceMock.getReadOnlyContext(), secondDirContextMock); + + replay(); + + final PoolingContextSource poolingContextSource = new PoolingContextSource(); + poolingContextSource.setContextSource(contextSourceMock); + + //Get a context + final DirContext readOnlyContext1 = poolingContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext1, dirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, poolingContextSource.getNumActive()); + assertEquals(0, poolingContextSource.getNumIdle()); + + //Close the context + readOnlyContext1.close(); + assertEquals(0, poolingContextSource.getNumActive()); + assertEquals(1, poolingContextSource.getNumIdle()); + + //Get the context again + final DirContext readOnlyContext2 = poolingContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext2, dirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, poolingContextSource.getNumActive()); + assertEquals(0, poolingContextSource.getNumIdle()); + + //Get a new context + final DirContext readOnlyContext3 = poolingContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext3, secondDirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(2, poolingContextSource.getNumActive()); + assertEquals(0, poolingContextSource.getNumIdle()); + + //Close context + readOnlyContext2.close(); + assertEquals(1, poolingContextSource.getNumActive()); + assertEquals(1, poolingContextSource.getNumIdle()); + + //Close context + readOnlyContext3.close(); + assertEquals(0, poolingContextSource.getNumActive()); + assertEquals(2, poolingContextSource.getNumIdle()); + } + + public void testGetReadWriteContextPool() throws Exception { + MockControl secondDirContextControl = MockControl.createControl(DirContext.class); + DirContext secondDirContextMock = (DirContext) secondDirContextControl.getMock(); + + contextSourceControl.expectAndReturn(contextSourceMock.getReadWriteContext(), dirContextMock); + contextSourceControl.expectAndReturn(contextSourceMock.getReadWriteContext(), secondDirContextMock); + + replay(); + + final PoolingContextSource poolingContextSource = new PoolingContextSource(); + poolingContextSource.setContextSource(contextSourceMock); + + //Get a context + final DirContext readOnlyContext1 = poolingContextSource.getReadWriteContext(); + assertEquals(readOnlyContext1, dirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, poolingContextSource.getNumActive()); + assertEquals(0, poolingContextSource.getNumIdle()); + + //Close the context + readOnlyContext1.close(); + assertEquals(0, poolingContextSource.getNumActive()); + assertEquals(1, poolingContextSource.getNumIdle()); + + //Get the context again + final DirContext readOnlyContext2 = poolingContextSource.getReadWriteContext(); + assertEquals(readOnlyContext2, dirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, poolingContextSource.getNumActive()); + assertEquals(0, poolingContextSource.getNumIdle()); + + //Get a new context + final DirContext readOnlyContext3 = poolingContextSource.getReadWriteContext(); + assertEquals(readOnlyContext3, secondDirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(2, poolingContextSource.getNumActive()); + assertEquals(0, poolingContextSource.getNumIdle()); + + //Close context + readOnlyContext2.close(); + assertEquals(1, poolingContextSource.getNumActive()); + assertEquals(1, poolingContextSource.getNumIdle()); + + //Close context + readOnlyContext3.close(); + assertEquals(0, poolingContextSource.getNumActive()); + assertEquals(2, poolingContextSource.getNumIdle()); + } + + public void testGetContextException() throws Exception { + contextSourceControl.expectAndThrow(contextSourceMock.getReadWriteContext(), new RuntimeException("Problem getting context")); + + replay(); + + final PoolingContextSource poolingContextSource = new PoolingContextSource(); + poolingContextSource.setContextSource(contextSourceMock); + + try { + poolingContextSource.getReadWriteContext(); + fail("PoolingContextSource.getReadWriteContext should have thrown DataAccessResourceFailureException"); + } + catch (DataAccessResourceFailureException darfe) { + // Expected + } + } + + public void testGetReadOnlyLdapContext() throws Exception { + MockControl secondLdapContextControl = MockControl.createControl(LdapContext.class); + LdapContext secondLdapContextMock = (LdapContext) secondLdapContextControl.getMock(); + + secondLdapContextControl.replay(); + + contextSourceControl.expectAndReturn(contextSourceMock.getReadOnlyContext(), ldapContextMock); + contextSourceControl.expectAndReturn(contextSourceMock.getReadOnlyContext(), secondLdapContextMock); + + replay(); + + final PoolingContextSource poolingContextSource = new PoolingContextSource(); + poolingContextSource.setContextSource(contextSourceMock); + + //Get a context + final DirContext readOnlyContext1 = poolingContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext1, ldapContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, poolingContextSource.getNumActive()); + assertEquals(0, poolingContextSource.getNumIdle()); + + //Close the context + readOnlyContext1.close(); + assertEquals(0, poolingContextSource.getNumActive()); + assertEquals(1, poolingContextSource.getNumIdle()); + + //Get the context again + final DirContext readOnlyContext2 = poolingContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext2, ldapContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, poolingContextSource.getNumActive()); + assertEquals(0, poolingContextSource.getNumIdle()); + + //Get a new context + final DirContext readOnlyContext3 = poolingContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext3, secondLdapContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(2, poolingContextSource.getNumActive()); + assertEquals(0, poolingContextSource.getNumIdle()); + + //Close context + readOnlyContext2.close(); + assertEquals(1, poolingContextSource.getNumActive()); + assertEquals(1, poolingContextSource.getNumIdle()); + + //Close context + readOnlyContext3.close(); + assertEquals(0, poolingContextSource.getNumActive()); + assertEquals(2, poolingContextSource.getNumIdle()); + + secondLdapContextControl.verify(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/pool/validation/DefaultDirContextValidatorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/pool/validation/DefaultDirContextValidatorTest.java new file mode 100644 index 00000000..2234622a --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/pool/validation/DefaultDirContextValidatorTest.java @@ -0,0 +1,187 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool.validation; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.pool.DirContextType; +import org.springframework.ldap.pool.validation.DefaultDirContextValidator; + +/** + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + */ +public class DefaultDirContextValidatorTest extends TestCase { + + private MockControl namingEnumerationControl; + + private NamingEnumeration namingEnumerationMock; + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + protected void setUp() throws Exception { + super.setUp(); + namingEnumerationControl = MockControl + .createControl(NamingEnumeration.class); + namingEnumerationMock = (NamingEnumeration) namingEnumerationControl + .getMock(); + + dirContextControl = MockControl.createControl(DirContext.class); + dirContextMock = (DirContext) dirContextControl.getMock(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + namingEnumerationControl = null; + namingEnumerationMock = null; + + dirContextControl = null; + dirContextMock = null; + } + + protected void replay() { + namingEnumerationControl.replay(); + dirContextControl.replay(); + } + + protected void verify() { + namingEnumerationControl.verify(); + dirContextControl.verify(); + } + + public void testProperties() throws Exception { + final DefaultDirContextValidator dirContextValidator = new DefaultDirContextValidator(); + + dirContextValidator.setBase("baseName"); + final String baseName = dirContextValidator.getBase(); + assertEquals("baseName", baseName); + + try { + dirContextValidator.setFilter(null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + dirContextValidator.setFilter("filter"); + final String filter = dirContextValidator.getFilter(); + assertEquals("filter", filter); + + try { + dirContextValidator.setSearchControls(null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + final SearchControls sc = new SearchControls(); + dirContextValidator.setSearchControls(sc); + final SearchControls sc2 = dirContextValidator.getSearchControls(); + assertEquals(sc, sc2); + } + + public void testValidateDirContextAssertions() throws Exception { + final DefaultDirContextValidator dirContextValidator = new DefaultDirContextValidator(); + + try { + dirContextValidator.validateDirContext(DirContextType.READ_ONLY, + null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + replay(); + + try { + dirContextValidator.validateDirContext(null, dirContextMock); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testValidateDirContextHasResult() throws Exception { + final DefaultDirContextValidator dirContextValidator = new DefaultDirContextValidator(); + + final String baseName = dirContextValidator.getBase(); + final String filter = dirContextValidator.getFilter(); + final SearchControls searchControls = dirContextValidator + .getSearchControls(); + + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), true); + dirContextControl.expectAndReturn(dirContextMock.search(baseName, + filter, searchControls), namingEnumerationMock); + + replay(); + + final boolean valid = dirContextValidator.validateDirContext( + DirContextType.READ_ONLY, dirContextMock); + + verify(); + assertTrue(valid); + } + + public void testValidateDirContextNoResult() throws Exception { + final DefaultDirContextValidator dirContextValidator = new DefaultDirContextValidator(); + + final String baseName = dirContextValidator.getBase(); + final String filter = dirContextValidator.getFilter(); + final SearchControls searchControls = dirContextValidator + .getSearchControls(); + + namingEnumerationControl.expectAndReturn(namingEnumerationMock + .hasMore(), false); + dirContextControl.expectAndReturn(dirContextMock.search(baseName, + filter, searchControls), namingEnumerationMock); + + replay(); + + final boolean valid = dirContextValidator.validateDirContext( + DirContextType.READ_ONLY, dirContextMock); + + verify(); + assertFalse(valid); + } + + public void testValidateDirContextException() throws Exception { + final DefaultDirContextValidator dirContextValidator = new DefaultDirContextValidator(); + + final String baseName = dirContextValidator.getBase(); + final String filter = dirContextValidator.getFilter(); + final SearchControls searchControls = dirContextValidator + .getSearchControls(); + + dirContextControl.expectAndThrow(dirContextMock.search(baseName, + filter, searchControls), + new NamingException("Failed to search")); + + replay(); + + final boolean valid = dirContextValidator.validateDirContext( + DirContextType.READ_ONLY, dirContextMock); + + verify(); + assertFalse(valid); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/BindOperationExecutorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/BindOperationExecutorTest.java new file mode 100644 index 00000000..116a8458 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/BindOperationExecutorTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.directory.BasicAttributes; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.transaction.compensating.BindOperationExecutor; + +public class BindOperationExecutorTest extends TestCase { + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + } + + protected void replay() { + ldapOperationsControl.replay(); + } + + protected void verify() { + ldapOperationsControl.verify(); + } + + public void testPerformOperation() { + DistinguishedName expectedDn = new DistinguishedName("cn=john doe"); + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + BindOperationExecutor tested = new BindOperationExecutor( + ldapOperationsMock, expectedDn, expectedObject, + expectedAttributes); + + ldapOperationsMock.bind(expectedDn, expectedObject, expectedAttributes); + + replay(); + // perform teste + tested.performOperation(); + verify(); + } + + public void testCommit() { + DistinguishedName expectedDn = new DistinguishedName("cn=john doe"); + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + BindOperationExecutor tested = new BindOperationExecutor( + ldapOperationsMock, expectedDn, expectedObject, + expectedAttributes); + + // Nothing to do here. + + replay(); + // perform teste + tested.commit(); + verify(); + } + + public void testRollback() { + DistinguishedName expectedDn = new DistinguishedName("cn=john doe"); + BindOperationExecutor tested = new BindOperationExecutor( + ldapOperationsMock, expectedDn, null, null); + + ldapOperationsMock.unbind(expectedDn); + + replay(); + // perform teste + tested.rollback(); + verify(); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/BindOperationRecorderTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/BindOperationRecorderTest.java new file mode 100644 index 00000000..414cf291 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/BindOperationRecorderTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.directory.BasicAttributes; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.transaction.compensating.BindOperationExecutor; +import org.springframework.ldap.transaction.compensating.BindOperationRecorder; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +public class BindOperationRecorderTest extends TestCase { + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + } + + public void testRecordOperation_DistinguishedName() { + BindOperationRecorder tested = new BindOperationRecorder( + ldapOperationsMock); + DistinguishedName expectedDn = new DistinguishedName("cn=John Doe"); + + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + // Perform test. + CompensatingTransactionOperationExecutor operation = tested + .recordOperation(new Object[] { expectedDn, expectedObject, + expectedAttributes }); + + assertTrue(operation instanceof BindOperationExecutor); + BindOperationExecutor rollbackOperation = (BindOperationExecutor) operation; + assertSame(expectedDn, rollbackOperation.getDn()); + assertSame(ldapOperationsMock, rollbackOperation.getLdapOperations()); + assertSame(expectedObject, rollbackOperation.getOriginalObject()); + assertSame(expectedAttributes, rollbackOperation + .getOriginalAttributes()); + } + + public void testPerformOperation_String() { + BindOperationRecorder tested = new BindOperationRecorder( + ldapOperationsMock); + String expectedDn = "cn=John Doe"; + + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + // Perform test. + CompensatingTransactionOperationExecutor operation = tested + .recordOperation(new Object[] { expectedDn, expectedObject, + expectedAttributes }); + + assertTrue(operation instanceof BindOperationExecutor); + BindOperationExecutor rollbackOperation = (BindOperationExecutor) operation; + assertEquals(expectedDn, rollbackOperation.getDn().toString()); + assertSame(ldapOperationsMock, rollbackOperation.getLdapOperations()); + } + + public void testPerformOperation_Invalid() { + BindOperationRecorder tested = new BindOperationRecorder( + ldapOperationsMock); + Object expectedDn = new Object(); + + try { + // Perform test. + tested.recordOperation(new Object[] { expectedDn }); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactoryTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactoryTest.java new file mode 100644 index 00000000..2a768b50 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/LdapCompensatingTransactionOperationFactoryTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.directory.DirContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; + +public class LdapCompensatingTransactionOperationFactoryTest extends TestCase { + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + private MockControl renamingStrategyControl; + + private TempEntryRenamingStrategy renamingStrategyMock; + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + private LdapCompensatingTransactionOperationFactory tested; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + + renamingStrategyControl = MockControl + .createControl(TempEntryRenamingStrategy.class); + renamingStrategyMock = (TempEntryRenamingStrategy) renamingStrategyControl + .getMock(); + + dirContextControl = MockControl.createControl(DirContext.class); + dirContextMock = (DirContext) dirContextControl.getMock(); + + tested = new LdapCompensatingTransactionOperationFactory( + renamingStrategyMock) { + + LdapOperations createLdapOperationsInstance(DirContext ctx) { + assertEquals(dirContextMock, ctx); + return ldapOperationsMock; + } + }; + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + + renamingStrategyControl = null; + renamingStrategyMock = null; + + dirContextControl = null; + dirContextMock = null; + + tested = null; + } + + protected void replay() { + ldapOperationsControl.replay(); + renamingStrategyControl.replay(); + dirContextControl.replay(); + } + + protected void verify() { + ldapOperationsControl.verify(); + renamingStrategyControl.verify(); + dirContextControl.verify(); + } + + public void testGetRecordingOperation_Bind() throws Exception { + + CompensatingTransactionOperationRecorder result = tested + .createRecordingOperation(dirContextMock, "bind"); + assertTrue(result instanceof BindOperationRecorder); + BindOperationRecorder bindOperationRecorder = (BindOperationRecorder) result; + assertSame(ldapOperationsMock, bindOperationRecorder + .getLdapOperations()); + } + + public void testGetRecordingOperation_Rebind() throws Exception { + CompensatingTransactionOperationRecorder result = tested + .createRecordingOperation(dirContextMock, "rebind"); + assertTrue(result instanceof RebindOperationRecorder); + RebindOperationRecorder rebindOperationRecorder = (RebindOperationRecorder) result; + assertSame(ldapOperationsMock, rebindOperationRecorder + .getLdapOperations()); + assertSame(renamingStrategyMock, rebindOperationRecorder + .getRenamingStrategy()); + } + + public void testGetRecordingOperation_Rename() throws Exception { + CompensatingTransactionOperationRecorder result = tested + .createRecordingOperation(dirContextMock, "rename"); + assertTrue(result instanceof RenameOperationRecorder); + RenameOperationRecorder recordingOperation = (RenameOperationRecorder) result; + assertSame(ldapOperationsMock, recordingOperation.getLdapOperations()); + } + + public void testGetRecordingOperation_ModifyAttributes() throws Exception { + CompensatingTransactionOperationRecorder result = tested + .createRecordingOperation(dirContextMock, "modifyAttributes"); + assertTrue(result instanceof ModifyAttributesOperationRecorder); + ModifyAttributesOperationRecorder recordingOperation = (ModifyAttributesOperationRecorder) result; + assertSame(ldapOperationsMock, recordingOperation.getLdapOperations()); + } + + public void testGetRecordingOperation_Unbind() throws Exception { + CompensatingTransactionOperationRecorder result = tested + .createRecordingOperation(dirContextMock, "unbind"); + assertTrue(result instanceof UnbindOperationRecorder); + UnbindOperationRecorder recordingOperation = (UnbindOperationRecorder) result; + assertSame(ldapOperationsMock, recordingOperation.getLdapOperations()); + assertSame(renamingStrategyMock, recordingOperation + .getRenamingStrategy()); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/LdapTransactionUtilsTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/LdapTransactionUtilsTest.java new file mode 100644 index 00000000..5eed68e4 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/LdapTransactionUtilsTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.support.LdapUtils; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +public class LdapTransactionUtilsTest extends TestCase { + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + protected void setUp() throws Exception { + dirContextControl = MockControl.createControl(DirContext.class); + dirContextMock = (DirContext) dirContextControl.getMock(); + + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clearSynchronization(); + } + } + + protected void tearDown() throws Exception { + dirContextControl = null; + dirContextMock = null; + } + + protected void replay() { + dirContextControl.replay(); + } + + protected void verify() { + dirContextControl.verify(); + } + + public void testCloseContext() throws NamingException { + dirContextMock.close(); + + replay(); + LdapUtils.closeContext(dirContextMock); + verify(); + } + + public void testCloseContext_NullContext() throws NamingException { + replay(); + LdapUtils.closeContext(null); + verify(); + } + + public void testIsSupportedWriteTransactionOperation() { + assertTrue(LdapTransactionUtils + .isSupportedWriteTransactionOperation("bind")); + assertTrue(LdapTransactionUtils + .isSupportedWriteTransactionOperation("rebind")); + assertTrue(LdapTransactionUtils + .isSupportedWriteTransactionOperation("unbind")); + assertTrue(LdapTransactionUtils + .isSupportedWriteTransactionOperation("modifyAttributes")); + assertTrue(LdapTransactionUtils + .isSupportedWriteTransactionOperation("rename")); + assertFalse(LdapTransactionUtils + .isSupportedWriteTransactionOperation("lookup")); + assertFalse(LdapTransactionUtils + .isSupportedWriteTransactionOperation("search")); + } + + public void dummyMethod() { + + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutorTest.java new file mode 100644 index 00000000..5a5e4928 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationExecutorTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.Name; +import javax.naming.directory.ModificationItem; + +import org.easymock.MockControl; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.transaction.compensating.ModifyAttributesOperationExecutor; + +import junit.framework.TestCase; + +public class ModifyAttributesOperationExecutorTest extends TestCase { + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + } + + protected void replay() { + ldapOperationsControl.replay(); + } + + protected void verify() { + ldapOperationsControl.verify(); + } + + public void testPerformOperation() { + ModificationItem[] expectedCompensatingItems = new ModificationItem[0]; + ModificationItem[] expectedActualItems = new ModificationItem[0]; + + Name expectedDn = new DistinguishedName("cn=john doe"); + + ModifyAttributesOperationExecutor tested = new ModifyAttributesOperationExecutor( + ldapOperationsMock, expectedDn, expectedActualItems, + expectedCompensatingItems); + + ldapOperationsMock.modifyAttributes(expectedDn, expectedActualItems); + + replay(); + // Perform test + tested.performOperation(); + + verify(); + } + + public void testCommit() { + ModificationItem[] expectedCompensatingItems = new ModificationItem[0]; + ModificationItem[] expectedActualItems = new ModificationItem[0]; + + Name expectedDn = new DistinguishedName("cn=john doe"); + + ModifyAttributesOperationExecutor tested = new ModifyAttributesOperationExecutor( + ldapOperationsMock, expectedDn, expectedActualItems, + expectedCompensatingItems); + + // No operation here + + replay(); + // Perform test + tested.commit(); + + verify(); + } + + public void testRollback() { + ModificationItem[] expectedCompensatingItems = new ModificationItem[0]; + ModificationItem[] expectedActualItems = new ModificationItem[0]; + + Name expectedDn = new DistinguishedName("cn=john doe"); + + ModifyAttributesOperationExecutor tested = new ModifyAttributesOperationExecutor( + ldapOperationsMock, expectedDn, expectedActualItems, + expectedCompensatingItems); + + ldapOperationsMock.modifyAttributes(expectedDn, + expectedCompensatingItems); + + replay(); + // Perform test + tested.rollback(); + + verify(); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorderTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorderTest.java new file mode 100644 index 00000000..6da35c9f --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/ModifyAttributesOperationRecorderTest.java @@ -0,0 +1,271 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +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.DirContext; +import javax.naming.directory.ModificationItem; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.transaction.compensating.ModifyAttributesOperationExecutor; +import org.springframework.ldap.transaction.compensating.ModifyAttributesOperationRecorder; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +public class ModifyAttributesOperationRecorderTest extends TestCase { + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + private MockControl attributesMapperControl; + + private AttributesMapper attributesMapperMock; + + private ModifyAttributesOperationRecorder tested; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + + attributesMapperControl = MockControl + .createControl(AttributesMapper.class); + attributesMapperMock = (AttributesMapper) attributesMapperControl + .getMock(); + + tested = new ModifyAttributesOperationRecorder(ldapOperationsMock); + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + + attributesMapperControl = null; + attributesMapperMock = null; + + tested = null; + } + + protected void replay() { + ldapOperationsControl.replay(); + attributesMapperControl.replay(); + } + + protected void verify() { + ldapOperationsControl.verify(); + attributesMapperControl.verify(); + } + + public void testRecordOperation() { + final ModificationItem incomingItem = new ModificationItem( + DirContext.ADD_ATTRIBUTE, new BasicAttribute("attribute1")); + ModificationItem[] incomingMods = new ModificationItem[] { incomingItem }; + final ModificationItem compensatingItem = new ModificationItem( + DirContext.ADD_ATTRIBUTE, new BasicAttribute("attribute2")); + + final Attributes expectedAttributes = new BasicAttributes(); + + tested = new ModifyAttributesOperationRecorder(ldapOperationsMock) { + AttributesMapper getAttributesMapper() { + return attributesMapperMock; + } + + protected ModificationItem getCompensatingModificationItem( + Attributes originalAttributes, + ModificationItem modificationItem) { + assertSame(expectedAttributes, originalAttributes); + assertSame(incomingItem, modificationItem); + return compensatingItem; + } + }; + + DistinguishedName expectedName = new DistinguishedName("cn=john doe"); + ldapOperationsControl.setDefaultMatcher(MockControl.ARRAY_MATCHER); + ldapOperationsControl.expectAndReturn(ldapOperationsMock.lookup( + expectedName, new String[] { "attribute1" }, + attributesMapperMock), expectedAttributes); + + replay(); + // Perform test + CompensatingTransactionOperationExecutor operation = tested + .recordOperation(new Object[] { expectedName, incomingMods }); + verify(); + + // Verify outcome + assertTrue(operation instanceof ModifyAttributesOperationExecutor); + ModifyAttributesOperationExecutor rollbackOperation = (ModifyAttributesOperationExecutor) operation; + assertSame(expectedName, rollbackOperation.getDn()); + assertSame(ldapOperationsMock, rollbackOperation.getLdapOperations()); + assertSame(incomingMods, rollbackOperation.getActualModifications()); + assertEquals(1, rollbackOperation.getCompensatingModifications().length); + assertSame(compensatingItem, rollbackOperation + .getCompensatingModifications()[0]); + } + + public void testGetCompensatingModificationItem_RemoveFullExistingAttribute() + throws NamingException { + BasicAttribute attribute = new BasicAttribute("someattr"); + attribute.add("value1"); + attribute.add("value2"); + Attributes attributes = new BasicAttributes(); + attributes.put(attribute); + + ModificationItem originalItem = new ModificationItem( + DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("someattr")); + + // Perform test + ModificationItem result = tested.getCompensatingModificationItem( + attributes, originalItem); + + // Verify result + assertEquals(DirContext.ADD_ATTRIBUTE, result.getModificationOp()); + Attribute resultAttribute = result.getAttribute(); + assertEquals("someattr", resultAttribute.getID()); + Object object = resultAttribute.get(0); + assertEquals("value1", object); + assertEquals("value2", resultAttribute.get(1)); + } + + public void testGetCompensatingModificationItem_RemoveTwoAttributeValues() + throws NamingException { + BasicAttribute attribute = new BasicAttribute("someattr"); + attribute.add("value1"); + attribute.add("value2"); + attribute.add("value3"); + Attributes attributes = new BasicAttributes(); + attributes.put(attribute); + + BasicAttribute modificationAttribute = new BasicAttribute("someattr"); + modificationAttribute.add("value1"); + modificationAttribute.add("value2"); + ModificationItem originalItem = new ModificationItem( + DirContext.REMOVE_ATTRIBUTE, modificationAttribute); + + // Perform test + ModificationItem result = tested.getCompensatingModificationItem( + attributes, originalItem); + + // Verify result + assertEquals(DirContext.ADD_ATTRIBUTE, result.getModificationOp()); + Attribute resultAttribute = result.getAttribute(); + assertEquals("someattr", resultAttribute.getID()); + Object object = resultAttribute.get(0); + assertEquals("value1", object); + assertEquals("value2", resultAttribute.get(1)); + } + + public void testGetCompensatingModificationItem_ReplaceExistingAttribute() + throws NamingException { + BasicAttribute attribute = new BasicAttribute("someattr"); + attribute.add("value1"); + attribute.add("value2"); + Attributes attributes = new BasicAttributes(); + attributes.put(attribute); + + BasicAttribute modificationAttribute = new BasicAttribute("someattr"); + modificationAttribute.add("newvalue1"); + modificationAttribute.add("newvalue2"); + ModificationItem originalItem = new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("someattr")); + + // Perform test + ModificationItem result = tested.getCompensatingModificationItem( + attributes, originalItem); + + // Verify result + assertEquals(DirContext.REPLACE_ATTRIBUTE, result.getModificationOp()); + Attribute resultAttribute = result.getAttribute(); + assertEquals("someattr", resultAttribute.getID()); + Object object = resultAttribute.get(0); + assertEquals("value1", object); + assertEquals("value2", resultAttribute.get(1)); + } + + public void testGetCompensatingModificationItem_ReplaceNonExistingAttribute() + throws NamingException { + Attributes attributes = new BasicAttributes(); + + BasicAttribute modificationAttribute = new BasicAttribute("someattr"); + modificationAttribute.add("newvalue1"); + modificationAttribute.add("newvalue2"); + ModificationItem originalItem = new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, modificationAttribute); + + // Perform test + ModificationItem result = tested.getCompensatingModificationItem( + attributes, originalItem); + + // Verify result + assertEquals(DirContext.REMOVE_ATTRIBUTE, result.getModificationOp()); + Attribute resultAttribute = result.getAttribute(); + assertEquals("someattr", resultAttribute.getID()); + assertEquals(0, resultAttribute.size()); + } + + public void testGetCompensatingModificationItem_AddNonExistingAttribute() + throws NamingException { + Attributes attributes = new BasicAttributes(); + + BasicAttribute modificationAttribute = new BasicAttribute("someattr"); + modificationAttribute.add("newvalue1"); + modificationAttribute.add("newvalue2"); + ModificationItem originalItem = new ModificationItem( + DirContext.ADD_ATTRIBUTE, modificationAttribute); + + // Perform test + ModificationItem result = tested.getCompensatingModificationItem( + attributes, originalItem); + + // Verify result + assertEquals(DirContext.REMOVE_ATTRIBUTE, result.getModificationOp()); + Attribute resultAttribute = result.getAttribute(); + assertEquals("someattr", resultAttribute.getID()); + assertEquals(0, resultAttribute.size()); + } + + public void testGetCompensatingModificationItem_AddExistingAttribute() + throws NamingException { + BasicAttribute attribute = new BasicAttribute("someattr"); + attribute.add("value1"); + attribute.add("value2"); + Attributes attributes = new BasicAttributes(); + attributes.put(attribute); + + BasicAttribute modificationAttribute = new BasicAttribute("someattr"); + modificationAttribute.add("newvalue1"); + modificationAttribute.add("newvalue2"); + ModificationItem originalItem = new ModificationItem( + DirContext.ADD_ATTRIBUTE, new BasicAttribute("someattr")); + + // Perform test + ModificationItem result = tested.getCompensatingModificationItem( + attributes, originalItem); + + // Verify result + assertEquals(DirContext.REPLACE_ATTRIBUTE, result.getModificationOp()); + Attribute resultAttribute = result.getAttribute(); + assertEquals("someattr", resultAttribute.getID()); + assertEquals("value1", result.getAttribute().get(0)); + assertEquals("value2", result.getAttribute().get(1)); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RebindOperationExecutorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RebindOperationExecutorTest.java new file mode 100644 index 00000000..6adb47c3 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RebindOperationExecutorTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.directory.BasicAttributes; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.transaction.compensating.RebindOperationExecutor; + +public class RebindOperationExecutorTest extends TestCase { + + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + } + + protected void replay() { + ldapOperationsControl.replay(); + } + + protected void verify() { + ldapOperationsControl.verify(); + } + + public void testPerformOperation() { + DistinguishedName expectedOriginalDn = new DistinguishedName( + "cn=john doe"); + DistinguishedName expectedTempDn = new DistinguishedName( + "cn=john doe_temp"); + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + RebindOperationExecutor tested = new RebindOperationExecutor( + ldapOperationsMock, expectedOriginalDn, expectedTempDn, + expectedObject, expectedAttributes); + + ldapOperationsMock.rename(expectedOriginalDn, expectedTempDn); + ldapOperationsMock.bind(expectedOriginalDn, expectedObject, + expectedAttributes); + + replay(); + // perform test + tested.performOperation(); + verify(); + } + + public void testCommit() { + DistinguishedName expectedOriginalDn = new DistinguishedName( + "cn=john doe"); + DistinguishedName expectedTempDn = new DistinguishedName( + "cn=john doe_temp"); + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + RebindOperationExecutor tested = new RebindOperationExecutor( + ldapOperationsMock, expectedOriginalDn, expectedTempDn, + expectedObject, expectedAttributes); + + ldapOperationsMock.unbind(expectedTempDn); + + replay(); + // perform test + tested.commit(); + verify(); + } + + public void testRollback() { + DistinguishedName expectedOriginalDn = new DistinguishedName( + "cn=john doe"); + DistinguishedName expectedTempDn = new DistinguishedName( + "cn=john doe_temp"); + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + RebindOperationExecutor tested = new RebindOperationExecutor( + ldapOperationsMock, expectedOriginalDn, expectedTempDn, + expectedObject, expectedAttributes); + + ldapOperationsMock.unbind(expectedOriginalDn); + ldapOperationsMock.rename(expectedTempDn, expectedOriginalDn); + + replay(); + // perform test + tested.rollback(); + verify(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RebindOperationRecorderTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RebindOperationRecorderTest.java new file mode 100644 index 00000000..7be40068 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RebindOperationRecorderTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import javax.naming.directory.BasicAttributes; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.transaction.compensating.RebindOperationExecutor; +import org.springframework.ldap.transaction.compensating.RebindOperationRecorder; +import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +public class RebindOperationRecorderTest extends TestCase { + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + private MockControl renamingStrategyControl; + + private TempEntryRenamingStrategy renamingStrategyMock; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + + renamingStrategyControl = MockControl + .createControl(TempEntryRenamingStrategy.class); + renamingStrategyMock = (TempEntryRenamingStrategy) renamingStrategyControl + .getMock(); + + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + + renamingStrategyControl = null; + renamingStrategyMock = null; + + } + + protected void replay() { + ldapOperationsControl.replay(); + renamingStrategyControl.replay(); + } + + protected void verify() { + ldapOperationsControl.verify(); + renamingStrategyControl.verify(); + } + + public void testRecordOperation() { + final DistinguishedName expectedDn = new DistinguishedName( + "cn=john doe"); + final DistinguishedName expectedTempDn = new DistinguishedName( + "cn=john doe"); + RebindOperationRecorder tested = new RebindOperationRecorder( + ldapOperationsMock, renamingStrategyMock); + + renamingStrategyControl.expectAndReturn(renamingStrategyMock + .getTemporaryName(expectedDn), expectedTempDn); + + replay(); + Object expectedObject = new Object(); + BasicAttributes expectedAttributes = new BasicAttributes(); + + // perform test + CompensatingTransactionOperationExecutor result = tested + .recordOperation(new Object[] { expectedDn, expectedObject, + expectedAttributes }); + verify(); + + assertTrue(result instanceof RebindOperationExecutor); + RebindOperationExecutor rollbackOperation = (RebindOperationExecutor) result; + assertSame(ldapOperationsMock, rollbackOperation.getLdapOperations()); + assertSame(expectedDn, rollbackOperation.getOriginalDn()); + assertSame(expectedTempDn, rollbackOperation.getTemporaryDn()); + assertSame(expectedObject, rollbackOperation.getOriginalObject()); + assertSame(expectedAttributes, rollbackOperation + .getOriginalAttributes()); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RenameOperationExecutorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RenameOperationExecutorTest.java new file mode 100644 index 00000000..79a97c0d --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RenameOperationExecutorTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import org.easymock.MockControl; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.transaction.compensating.RenameOperationExecutor; + +import junit.framework.TestCase; + +public class RenameOperationExecutorTest extends TestCase { + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + } + + protected void replay() { + ldapOperationsControl.replay(); + } + + protected void verify() { + ldapOperationsControl.verify(); + } + + public void testPerformOperation() { + DistinguishedName expectedNewName = new DistinguishedName("ou=newOu"); + DistinguishedName expectedOldName = new DistinguishedName("ou=someou"); + RenameOperationExecutor tested = new RenameOperationExecutor( + ldapOperationsMock, expectedOldName, expectedNewName); + + ldapOperationsMock.rename(expectedOldName, expectedNewName); + + replay(); + // Perform test. + tested.performOperation(); + verify(); + } + + public void testCommit() { + DistinguishedName expectedNewName = new DistinguishedName("ou=newOu"); + DistinguishedName expectedOldName = new DistinguishedName("ou=someou"); + RenameOperationExecutor tested = new RenameOperationExecutor( + ldapOperationsMock, expectedOldName, expectedNewName); + + // Nothing to do for this operation. + + replay(); + // Perform test. + tested.commit(); + verify(); + } + + public void testRollback() { + DistinguishedName expectedNewName = new DistinguishedName("ou=newOu"); + DistinguishedName expectedOldName = new DistinguishedName("ou=someou"); + RenameOperationExecutor tested = new RenameOperationExecutor( + ldapOperationsMock, expectedOldName, expectedNewName); + + ldapOperationsMock.rename(expectedNewName, expectedOldName); + + replay(); + // Perform test. + tested.rollback(); + verify(); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RenameOperationRecorderTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RenameOperationRecorderTest.java new file mode 100644 index 00000000..2c3a3310 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/RenameOperationRecorderTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.transaction.compensating.RenameOperationExecutor; +import org.springframework.ldap.transaction.compensating.RenameOperationRecorder; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +public class RenameOperationRecorderTest extends TestCase { + + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + } + + protected void replay() { + ldapOperationsControl.replay(); + } + + protected void verify() { + ldapOperationsControl.verify(); + } + + public void testRecordOperation() { + RenameOperationRecorder tested = new RenameOperationRecorder( + ldapOperationsMock); + + replay(); + // Perform test + CompensatingTransactionOperationExecutor operation = tested + .recordOperation(new Object[] { "ou=someou", "ou=newou" }); + verify(); + + assertTrue(operation instanceof RenameOperationExecutor); + RenameOperationExecutor rollbackOperation = (RenameOperationExecutor) operation; + assertSame(ldapOperationsMock, rollbackOperation.getLdapOperations()); + assertEquals("ou=newou", rollbackOperation.getNewDn().toString()); + assertEquals("ou=someou", rollbackOperation.getOriginalDn().toString()); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/UnbindOperationExecutorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/UnbindOperationExecutorTest.java new file mode 100644 index 00000000..e84da183 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/UnbindOperationExecutorTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.transaction.compensating.UnbindOperationExecutor; + +public class UnbindOperationExecutorTest extends TestCase { + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + + } + + protected void replay() { + ldapOperationsControl.replay(); + } + + protected void verify() { + ldapOperationsControl.verify(); + } + + public void testPerformOperation() { + DistinguishedName expectedOldName = new DistinguishedName("cn=oldDn"); + DistinguishedName expectedTempName = new DistinguishedName("cn=newDn"); + UnbindOperationExecutor tested = new UnbindOperationExecutor( + ldapOperationsMock, expectedOldName, expectedTempName); + + ldapOperationsMock.rename(expectedOldName, expectedTempName); + + replay(); + // Perform test + tested.performOperation(); + verify(); + } + + public void testCommit() { + DistinguishedName expectedOldName = new DistinguishedName("cn=oldDn"); + DistinguishedName expectedTempName = new DistinguishedName("cn=newDn"); + UnbindOperationExecutor tested = new UnbindOperationExecutor( + ldapOperationsMock, expectedOldName, expectedTempName); + + ldapOperationsMock.unbind(expectedTempName); + + replay(); + // Perform test + tested.commit(); + verify(); + } + + public void testRollback() { + DistinguishedName expectedOldName = new DistinguishedName("cn=oldDn"); + DistinguishedName expectedTempName = new DistinguishedName("cn=newDn"); + UnbindOperationExecutor tested = new UnbindOperationExecutor( + ldapOperationsMock, expectedOldName, expectedTempName); + + ldapOperationsMock.rename(expectedTempName, expectedOldName); + + replay(); + // Perform test + tested.rollback(); + verify(); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/UnbindOperationRecorderTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/UnbindOperationRecorderTest.java new file mode 100644 index 00000000..8dca6f54 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/UnbindOperationRecorderTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; +import org.springframework.ldap.transaction.compensating.UnbindOperationExecutor; +import org.springframework.ldap.transaction.compensating.UnbindOperationRecorder; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; + +public class UnbindOperationRecorderTest extends TestCase { + private MockControl ldapOperationsControl; + + private LdapOperations ldapOperationsMock; + + private MockControl renamingStrategyControl; + + private TempEntryRenamingStrategy renamingStrategyMock; + + protected void setUp() throws Exception { + ldapOperationsControl = MockControl.createControl(LdapOperations.class); + ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock(); + + renamingStrategyControl = MockControl + .createControl(TempEntryRenamingStrategy.class); + renamingStrategyMock = (TempEntryRenamingStrategy) renamingStrategyControl + .getMock(); + + } + + protected void tearDown() throws Exception { + ldapOperationsControl = null; + ldapOperationsMock = null; + + renamingStrategyControl = null; + renamingStrategyMock = null; + + } + + protected void replay() { + ldapOperationsControl.replay(); + renamingStrategyControl.replay(); + } + + protected void verify() { + ldapOperationsControl.verify(); + renamingStrategyControl.verify(); + } + + public void testRecordOperation() { + final DistinguishedName expectedTempName = new DistinguishedName( + "cn=john doe_temp"); + final DistinguishedName expectedDn = new DistinguishedName( + "cn=john doe"); + UnbindOperationRecorder tested = new UnbindOperationRecorder( + ldapOperationsMock, renamingStrategyMock); + + renamingStrategyControl.expectAndReturn(renamingStrategyMock + .getTemporaryName(expectedDn), expectedTempName); + + replay(); + // Perform test + CompensatingTransactionOperationExecutor operation = tested + .recordOperation(new Object[] { expectedDn }); + verify(); + + // Verify result + assertTrue(operation instanceof UnbindOperationExecutor); + UnbindOperationExecutor rollbackOperation = (UnbindOperationExecutor) operation; + assertSame(ldapOperationsMock, rollbackOperation.getLdapOperations()); + assertSame(expectedDn, rollbackOperation.getOriginalDn()); + assertSame(expectedTempName, rollbackOperation.getTemporaryDn()); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerTest.java new file mode 100644 index 00000000..3a044c1d --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerTest.java @@ -0,0 +1,201 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.transaction.compensating.manager; + +import javax.naming.directory.DirContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; +import org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager; +import org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import org.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport; +import org.springframework.transaction.compensating.support.CompensatingTransactionObject; +import org.springframework.transaction.support.DefaultTransactionStatus; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +public class ContextSourceTransactionManagerTest extends TestCase { + + private MockControl contextSourceControl; + + private ContextSource contextSourceMock; + + private MockControl contextControl; + + private DirContext contextMock; + + private ContextSourceTransactionManager tested; + + private MockControl transactionDefinitionControl; + + private MockControl transactionDataManagerControl; + + private CompensatingTransactionOperationManager transactionDataManagerMock; + + private TransactionDefinition transactionDefinitionMock; + + private MockControl renamingStrategyControl; + + private TempEntryRenamingStrategy renamingStrategyMock; + + protected void setUp() throws Exception { + super.setUp(); + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clearSynchronization(); + } + + contextSourceControl = MockControl.createControl(ContextSource.class); + contextSourceMock = (ContextSource) contextSourceControl.getMock(); + + contextControl = MockControl.createControl(DirContext.class); + contextMock = (DirContext) contextControl.getMock(); + + transactionDefinitionControl = MockControl + .createControl(TransactionDefinition.class); + transactionDefinitionMock = (TransactionDefinition) transactionDefinitionControl + .getMock(); + + transactionDataManagerControl = MockControl + .createControl(CompensatingTransactionOperationManager.class); + transactionDataManagerMock = (CompensatingTransactionOperationManager) transactionDataManagerControl + .getMock(); + + renamingStrategyControl = MockControl + .createControl(TempEntryRenamingStrategy.class); + renamingStrategyMock = (TempEntryRenamingStrategy) renamingStrategyControl + .getMock(); + + tested = new ContextSourceTransactionManager(); + tested.setContextSource(contextSourceMock); + tested.setRenamingStrategy(renamingStrategyMock); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + contextControl = null; + contextMock = null; + + contextSourceControl = null; + contextSourceMock = null; + + transactionDefinitionControl = null; + transactionDefinitionMock = null; + + transactionDataManagerControl = null; + transactionDataManagerMock = null; + + renamingStrategyControl = null; + renamingStrategyMock = null; + + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clearSynchronization(); + } + } + + public void testDoGetTransaction() { + Object result = tested.doGetTransaction(); + + assertNotNull(result); + assertTrue(result instanceof CompensatingTransactionObject); + CompensatingTransactionObject transactionObject = (CompensatingTransactionObject) result; + assertNull(transactionObject.getHolder()); + } + + public void testDoGetTransactionTransactionActive() { + CompensatingTransactionHolderSupport expectedContextHolder = new DirContextHolder( + null, null); + TransactionSynchronizationManager.bindResource(contextSourceMock, + expectedContextHolder); + Object result = tested.doGetTransaction(); + assertSame(expectedContextHolder, + ((CompensatingTransactionObject) result).getHolder()); + } + + public void testDoBegin() { + contextSourceControl.expectAndReturn(contextSourceMock + .getReadOnlyContext(), contextMock); + + contextSourceControl.replay(); + + CompensatingTransactionObject expectedTransactionObject = new CompensatingTransactionObject( + null); + tested.doBegin(expectedTransactionObject, transactionDefinitionMock); + + contextSourceControl.verify(); + + DirContextHolder foundContextHolder = (DirContextHolder) TransactionSynchronizationManager + .getResource(contextSourceMock); + assertSame(contextMock, foundContextHolder.getCtx()); + } + + public void testDoCommit() { + } + + public void testDoRollback() { + + DirContextHolder expectedContextHolder = new DirContextHolder(null, + contextMock); + expectedContextHolder + .setTransactionOperationManager(transactionDataManagerMock); + TransactionSynchronizationManager.bindResource(contextSourceMock, + expectedContextHolder); + + transactionDataManagerMock.rollback(); + transactionDataManagerControl.replay(); + CompensatingTransactionObject transactionObject = new CompensatingTransactionObject( + null); + transactionObject.setHolder(expectedContextHolder); + tested.doRollback(new DefaultTransactionStatus(transactionObject, + false, false, false, false, null)); + transactionDataManagerControl.verify(); + } + + public void testDoCleanupAfterCompletion() throws Exception { + DirContextHolder expectedContextHolder = new DirContextHolder(null, + contextMock); + TransactionSynchronizationManager.bindResource(contextSourceMock, + expectedContextHolder); + + contextMock.close(); + contextControl.replay(); + + tested.doCleanupAfterCompletion(new CompensatingTransactionObject( + expectedContextHolder)); + + contextControl.verify(); + assertNull(TransactionSynchronizationManager + .getResource(contextSourceMock)); + assertNull(expectedContextHolder.getTransactionOperationManager()); + } + + public void testSetContextSource_Proxy() { + TransactionAwareContextSourceProxy proxy = new TransactionAwareContextSourceProxy( + contextSourceMock); + + // Perform test + tested.setContextSource(proxy); + ContextSource result = tested.getContextSource(); + + // Verify result + assertSame(contextSourceMock, result); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxyTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxyTest.java new file mode 100644 index 00000000..a2b3d544 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareContextSourceProxyTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.DirContextProxy; +import org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy; + +/** + * Tests for {@link TransactionAwareContextSourceProxy}. + * + * @author Mattias Arthursson + */ +public class TransactionAwareContextSourceProxyTest extends TestCase { + private MockControl contextSourceControl; + + private ContextSource contextSourceMock; + + private TransactionAwareContextSourceProxy tested; + + private MockControl ldapContextControl; + + private LdapContext ldapContextMock; + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + protected void setUp() throws Exception { + + contextSourceControl = MockControl.createControl(ContextSource.class); + contextSourceMock = (ContextSource) contextSourceControl.getMock(); + + ldapContextControl = MockControl.createControl(LdapContext.class); + ldapContextMock = (LdapContext) ldapContextControl.getMock(); + + dirContextControl = MockControl.createControl(DirContext.class); + dirContextMock = (DirContext) dirContextControl.getMock(); + + tested = new TransactionAwareContextSourceProxy(contextSourceMock); + } + + protected void tearDown() throws Exception { + contextSourceControl = null; + contextSourceMock = null; + + ldapContextControl = null; + ldapContextMock = null; + + dirContextControl = null; + dirContextMock = null; + + tested = null; + } + + public void testGetReadWriteContext_LdapContext() { + contextSourceControl.expectAndReturn(contextSourceMock + .getReadWriteContext(), ldapContextMock); + + contextSourceControl.replay(); + + DirContext result = tested.getReadWriteContext(); + + contextSourceControl.verify(); + + assertNotNull("Result should not be null", result); + assertTrue("Should be an LdapContext instance", + result instanceof LdapContext); + assertTrue("Should be a DirContextProxy instance", + result instanceof DirContextProxy); + } + + public void testGetReadWriteContext_DirContext() { + contextSourceControl.expectAndReturn(contextSourceMock + .getReadWriteContext(), dirContextMock); + + contextSourceControl.replay(); + + DirContext result = tested.getReadWriteContext(); + + contextSourceControl.verify(); + + assertNotNull("Result should not be null", result); + assertTrue("Should be a DirContext instance", + result instanceof DirContext); + assertFalse("Should not be an LdapContext instance", + result instanceof LdapContext); + assertTrue("Should be a DirContextProxy instance", + result instanceof DirContextProxy); + } + + public void testGetReadOnlyContext_LdapContext() { + contextSourceControl.expectAndReturn(contextSourceMock + .getReadWriteContext(), ldapContextMock); + + contextSourceControl.replay(); + + DirContext result = tested.getReadOnlyContext(); + + contextSourceControl.verify(); + + assertNotNull("Result should not be null", result); + assertTrue("Should be an LdapContext instance", + result instanceof LdapContext); + assertTrue("Should be a DirContextProxy instance", + result instanceof DirContextProxy); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandlerTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandlerTest.java new file mode 100644 index 00000000..3ebbcbad --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/manager/TransactionAwareDirContextInvocationHandlerTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.transaction.compensating.manager.TransactionAwareDirContextInvocationHandler; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +public class TransactionAwareDirContextInvocationHandlerTest extends TestCase { + + private MockControl contextSourceControl; + + private ContextSource contextSourceMock; + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + private TransactionAwareDirContextInvocationHandler tested; + + private DirContextHolder holder; + + protected void setUp() throws Exception { + dirContextControl = MockControl.createControl(DirContext.class); + dirContextMock = (DirContext) dirContextControl.getMock(); + + contextSourceControl = MockControl.createControl(ContextSource.class); + contextSourceMock = (ContextSource) contextSourceControl.getMock(); + + holder = new DirContextHolder(null, dirContextMock); + tested = new TransactionAwareDirContextInvocationHandler(null, null); + } + + protected void tearDown() throws Exception { + dirContextControl = null; + dirContextMock = null; + + contextSourceControl = null; + contextSourceMock = null; + + holder = null; + + tested = null; + } + + protected void replay() { + dirContextControl.replay(); + contextSourceControl.replay(); + } + + protected void verify() { + dirContextControl.verify(); + contextSourceControl.verify(); + } + + public void testDoCloseConnection_NoTransaction() throws NamingException { + dirContextMock.close(); + + replay(); + tested.doCloseConnection(dirContextMock, contextSourceMock); + verify(); + } + + public void testDoCloseConnection_ActiveTransaction() + throws NamingException { + TransactionSynchronizationManager.bindResource(contextSourceMock, + holder); + + // Context should not be closed. + + replay(); + tested.doCloseConnection(dirContextMock, contextSourceMock); + verify(); + } + + public void testDoCloseConnection_NotTransactionalContext() + throws NamingException { + TransactionSynchronizationManager.bindResource(contextSourceMock, + holder); + + MockControl dirContextControl2 = MockControl + .createControl(DirContext.class); + DirContext dirContextMock2 = (DirContext) dirContextControl2.getMock(); + + dirContextMock2.close(); + + dirContextControl2.replay(); + replay(); + tested.doCloseConnection(dirContextMock2, contextSourceMock); + verify(); + dirContextControl2.verify(); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategyTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategyTest.java new file mode 100644 index 00000000..e2812cc4 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/support/DefaultTempEntryRenamingStrategyTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.support; + +import javax.naming.Name; + +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.transaction.compensating.support.DefaultTempEntryRenamingStrategy; + +import junit.framework.TestCase; + +public class DefaultTempEntryRenamingStrategyTest extends TestCase { + + public void testGetTemporaryName() { + DistinguishedName expectedOriginalName = new DistinguishedName( + "cn=john doe, ou=somecompany, c=SE"); + DefaultTempEntryRenamingStrategy tested = new DefaultTempEntryRenamingStrategy(); + + Name result = tested.getTemporaryName(expectedOriginalName); + assertEquals("cn=john doe_temp, ou=somecompany, c=SE", result + .toString()); + assertNotSame(expectedOriginalName, result); + } + + public void testGetTemporaryDN_MultivalueDN() { + DistinguishedName expectedOriginalName = new DistinguishedName( + "cn=john doe+sn=doe, ou=somecompany, c=SE"); + DefaultTempEntryRenamingStrategy tested = new DefaultTempEntryRenamingStrategy(); + + Name result = tested.getTemporaryName(expectedOriginalName); + assertEquals("cn=john doe_temp+sn=doe, ou=somecompany, c=SE", result + .toString()); + } + + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategyTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategyTest.java new file mode 100644 index 00000000..86a27f99 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/transaction/compensating/support/DifferentSubtreeTempEntryRenamingStrategyTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.support; + +import javax.naming.Name; + +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.transaction.compensating.support.DifferentSubtreeTempEntryRenamingStrategy; + +import junit.framework.TestCase; + +public class DifferentSubtreeTempEntryRenamingStrategyTest extends TestCase { + + public void testGetTemporaryName() { + DistinguishedName originalName = new DistinguishedName( + "cn=john doe, ou=somecompany, c=SE"); + DifferentSubtreeTempEntryRenamingStrategy tested = new DifferentSubtreeTempEntryRenamingStrategy( + new DistinguishedName("ou=tempEntries")); + + int nextSequenceNo = tested.getNextSequenceNo(); + + // Perform test + Name result = tested.getTemporaryName(originalName); + + // Verify result + assertEquals("cn=john doe" + nextSequenceNo + ", ou=tempEntries", + result.toString()); + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/ldap/util/ListComparatorTest.java b/mvn-build/core/src/test/java/org/springframework/ldap/util/ListComparatorTest.java new file mode 100644 index 00000000..fde9bf9a --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/ldap/util/ListComparatorTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.util; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.ldap.support.ListComparator; + +import junit.framework.TestCase; + +/** + * Tests for ListComparator. + * + * @author Mattias Arthursson + */ +public class ListComparatorTest extends TestCase { + + private ListComparator tested; + + protected void setUp() throws Exception { + super.setUp(); + tested = new ListComparator(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testCompare_Equals() { + List list1 = Arrays.asList(new Object[] { new Integer(0), + new Integer(0) }); + List list2 = Arrays.asList(new Object[] { new Integer(0), + new Integer(0) }); + + int result = tested.compare(list1, list2); + assertEquals(0, result); + } + + public void testCompare_Less() { + List list1 = Arrays.asList(new Object[] { new Integer(0), + new Integer(0) }); + List list2 = Arrays.asList(new Object[] { new Integer(0), + new Integer(1) }); + + int result = tested.compare(list1, list2); + assertTrue(result < 0); + } + + public void testCompare_Greater() { + List list1 = Arrays.asList(new Object[] { new Integer(0), + new Integer(1) }); + List list2 = Arrays.asList(new Object[] { new Integer(0), + new Integer(0) }); + + int result = tested.compare(list1, list2); + assertTrue(result > 0); + } + + public void testCompare_Longer() { + List list1 = Arrays.asList(new Object[] { new Integer(0), + new Integer(0), new Integer(0) }); + List list2 = Arrays.asList(new Object[] { new Integer(0), + new Integer(0) }); + + int result = tested.compare(list1, list2); + assertTrue(result > 0); + } + + public void testCompare_Shorter() { + List list1 = Arrays.asList(new Object[] { new Integer(0), + new Integer(0) }); + List list2 = Arrays.asList(new Object[] { new Integer(0), + new Integer(0), new Integer(0) }); + + int result = tested.compare(list1, list2); + assertTrue(result < 0); + } +} diff --git a/mvn-build/core/src/test/java/org/springframework/transaction/compensating/support/CompensatingTransactionUtilsTest.java b/mvn-build/core/src/test/java/org/springframework/transaction/compensating/support/CompensatingTransactionUtilsTest.java new file mode 100644 index 00000000..e7a9b319 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/transaction/compensating/support/CompensatingTransactionUtilsTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.transaction.compensating.support; + +import java.lang.reflect.Method; + +import javax.naming.directory.DirContext; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.transaction.compensating.manager.DirContextHolder; +import org.springframework.transaction.compensating.CompensatingTransactionOperationManager; +import org.springframework.transaction.compensating.support.CompensatingTransactionHolderSupport; +import org.springframework.transaction.compensating.support.CompensatingTransactionUtils; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +public class CompensatingTransactionUtilsTest extends TestCase { + + private MockControl dirContextControl; + + private DirContext dirContextMock; + + private MockControl contextSourceControl; + + private ContextSource contextSourceMock; + + private MockControl operationManagerControl; + + private CompensatingTransactionOperationManager operationManagerMock; + + protected void setUp() throws Exception { + dirContextControl = MockControl.createControl(DirContext.class); + dirContextMock = (DirContext) dirContextControl.getMock(); + + contextSourceControl = MockControl.createControl(ContextSource.class); + contextSourceMock = (ContextSource) contextSourceControl.getMock(); + + operationManagerControl = MockControl + .createControl(CompensatingTransactionOperationManager.class); + operationManagerMock = (CompensatingTransactionOperationManager) operationManagerControl + .getMock(); + + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clearSynchronization(); + } + } + + protected void tearDown() throws Exception { + dirContextControl = null; + dirContextMock = null; + + contextSourceControl = null; + contextSourceMock = null; + + operationManagerControl = null; + operationManagerMock = null; + } + + protected void replay() { + dirContextControl.replay(); + contextSourceControl.replay(); + operationManagerControl.replay(); + } + + protected void verify() { + dirContextControl.verify(); + contextSourceControl.verify(); + operationManagerControl.verify(); + } + + public void testPerformOperation() throws Throwable { + CompensatingTransactionHolderSupport holder = new DirContextHolder( + null, dirContextMock); + holder.setTransactionOperationManager(operationManagerMock); + + TransactionSynchronizationManager.bindResource(contextSourceMock, + holder); + + Object[] expectedArgs = new Object[] { "someDn" }; + operationManagerMock.performOperation(dirContextMock, "unbind", + expectedArgs); + + replay(); + CompensatingTransactionUtils.performOperation(contextSourceMock, + dirContextMock, getUnbindMethod(), expectedArgs); + verify(); + } + + public void testPerformOperation_NoTransaction() throws Throwable { + Object[] expectedArgs = new Object[] { "someDn" }; + dirContextMock.unbind("someDn"); + + replay(); + CompensatingTransactionUtils.performOperation(contextSourceMock, + dirContextMock, getUnbindMethod(), expectedArgs); + verify(); + } + + private Method getUnbindMethod() throws NoSuchMethodException { + return DirContext.class.getMethod("unbind", + new Class[] { String.class }); + } + + public void dummyMethod() { + + } + +} diff --git a/mvn-build/core/src/test/java/org/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManagerTest.java b/mvn-build/core/src/test/java/org/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManagerTest.java new file mode 100644 index 00000000..b5c35185 --- /dev/null +++ b/mvn-build/core/src/test/java/org/springframework/transaction/compensating/support/DefaultCompensatingTransactionOperationManagerTest.java @@ -0,0 +1,168 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.transaction.compensating.support; + +import java.util.Stack; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor; +import org.springframework.transaction.compensating.CompensatingTransactionOperationFactory; +import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder; +import org.springframework.transaction.compensating.support.DefaultCompensatingTransactionOperationManager; + +public class DefaultCompensatingTransactionOperationManagerTest extends + TestCase { + + private MockControl operationExecutorControl; + + private CompensatingTransactionOperationExecutor operationExecutorMock; + + private MockControl operationFactoryControl; + + private CompensatingTransactionOperationFactory operationFactoryMock; + + private MockControl operationRecorderControl; + + private CompensatingTransactionOperationRecorder operationRecorderMock; + + protected void setUp() throws Exception { + super.setUp(); + operationExecutorControl = MockControl + .createControl(CompensatingTransactionOperationExecutor.class); + operationExecutorMock = (CompensatingTransactionOperationExecutor) operationExecutorControl + .getMock(); + + operationFactoryControl = MockControl + .createControl(CompensatingTransactionOperationFactory.class); + operationFactoryMock = (CompensatingTransactionOperationFactory) operationFactoryControl + .getMock(); + + operationRecorderControl = MockControl + .createControl(CompensatingTransactionOperationRecorder.class); + operationRecorderMock = (CompensatingTransactionOperationRecorder) operationRecorderControl + .getMock(); + + } + + protected void tearDown() throws Exception { + super.tearDown(); + operationExecutorControl = null; + operationExecutorMock = null; + + operationFactoryControl = null; + operationFactoryMock = null; + + operationRecorderControl = null; + operationRecorderMock = null; + } + + protected void replay() { + operationExecutorControl.replay(); + operationFactoryControl.replay(); + operationRecorderControl.replay(); + } + + protected void verify() { + operationExecutorControl.verify(); + operationFactoryControl.verify(); + operationRecorderControl.verify(); + } + + public void testPerformOperation() { + Object[] expectedArgs = new Object[0]; + Object expectedResource = new Object(); + + operationFactoryControl.expectAndReturn(operationFactoryMock + .createRecordingOperation(expectedResource, "some method"), + operationRecorderMock); + operationRecorderControl.expectAndReturn(operationRecorderMock + .recordOperation(expectedArgs), operationExecutorMock); + operationExecutorMock.performOperation(); + + DefaultCompensatingTransactionOperationManager tested = new DefaultCompensatingTransactionOperationManager( + operationFactoryMock); + replay(); + tested.performOperation(expectedResource, "some method", expectedArgs); + verify(); + + Stack result = tested.getOperationExecutors(); + assertFalse(result.isEmpty()); + assertSame(operationExecutorMock, result.peek()); + } + + public void testRollback() { + DefaultCompensatingTransactionOperationManager tested = new DefaultCompensatingTransactionOperationManager( + operationFactoryMock); + tested.getOperationExecutors().push(operationExecutorMock); + + operationExecutorMock.rollback(); + + replay(); + tested.rollback(); + verify(); + } + + public void testRollback_Exception() { + DefaultCompensatingTransactionOperationManager tested = new DefaultCompensatingTransactionOperationManager( + operationFactoryMock); + tested.getOperationExecutors().push(operationExecutorMock); + + operationExecutorMock.rollback(); + operationExecutorControl.setThrowable(new RuntimeException()); + + replay(); + try { + tested.rollback(); + fail("TransactionSystemException expected"); + } catch (TransactionSystemException expected) { + assertTrue(true); + } + verify(); + } + + public void testCommit() { + DefaultCompensatingTransactionOperationManager tested = new DefaultCompensatingTransactionOperationManager( + operationFactoryMock); + tested.getOperationExecutors().push(operationExecutorMock); + + operationExecutorMock.commit(); + + replay(); + tested.commit(); + verify(); + } + + public void testCommit_Exception() { + DefaultCompensatingTransactionOperationManager tested = new DefaultCompensatingTransactionOperationManager( + operationFactoryMock); + tested.getOperationExecutors().push(operationExecutorMock); + + operationExecutorMock.commit(); + operationExecutorControl.setThrowable(new RuntimeException()); + + replay(); + try { + tested.commit(); + fail("TransactionSystemException expected"); + } catch (TransactionSystemException expected) { + assertTrue(true); + } + verify(); + } +} diff --git a/mvn-build/core/src/test/resources/log4j.properties b/mvn-build/core/src/test/resources/log4j.properties new file mode 100644 index 00000000..66b1c2ec --- /dev/null +++ b/mvn-build/core/src/test/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootCategory=warn, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout.ConversionPattern=%r [%t] %-5p\: %-15c{2} - %m%n +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout diff --git a/mvn-build/core/xdocs/navigation.xml b/mvn-build/core/xdocs/navigation.xml new file mode 100644 index 00000000..84927d4c --- /dev/null +++ b/mvn-build/core/xdocs/navigation.xml @@ -0,0 +1,43 @@ + + + + + + + LdapTemplate + + + + + +

+ + + + + + + \ No newline at end of file diff --git a/mvn-build/integration-tests/core-integration-tests/pom.xml b/mvn-build/integration-tests/core-integration-tests/pom.xml new file mode 100644 index 00000000..c972debf --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + org.springframework.ldap + spring-ldap-core-integration-tests + Spring LDAP Core Integration Tests + 1.2.2-SNAPSHOT + + + 2.0.8 + + + + + + maven-eclipse-plugin + + true + + + + + maven-compiler-plugin + + 1.4 + 1.4 + + + + + + + + org.springframework.ldap + spring-ldap-test + 1.2.2-SNAPSHOT + + + commons-pool + commons-pool + 1.3 + provided + + + com.sun + ldapbp + 1.0 + system + ${basedir}/../../lib/ldapbp.jar + + + org.acegisecurity + acegi-security + 1.0.3 + provided + + + + org.springframework + spring-dao + ${spring.version} + provided + + + org.springframework + spring-jdbc + ${spring.version} + provided + + + org.springframework + spring-aop + ${spring.version} + test + + + aspectj + aspectjrt + 1.5.3 + test + + + aspectj + aspectjweaver + 1.5.3 + test + + + gsbase + gsbase + 2.0.1 + test + + + hsqldb + hsqldb + 1.8.0.7 + test + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/Person.java b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/Person.java new file mode 100644 index 00000000..50cae554 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/Person.java @@ -0,0 +1,64 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +/** + * Dummy bean to be used in the LdapTemplate integration tests. + * + * @author Mattias Arthursson + */ +public class Person { + private String fullname; + + private String lastname; + + private String description; + + private String phone; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getFullname() { + return fullname; + } + + public void setFullname(String fullname) { + this.fullname = fullname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/PersonAttributesMapper.java b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/PersonAttributesMapper.java new file mode 100644 index 00000000..6b0f0a4d --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/PersonAttributesMapper.java @@ -0,0 +1,47 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.AttributesMapper; + + +/** + * Dummy implementation of AttributesMapper for use in integration tests. + * + * @author Mattias Arthursson + * + */ +public class PersonAttributesMapper implements AttributesMapper { + + /** + * Maps the given attributes into a {@link Person} object. + * + * @see org.springframework.ldap.core.AttributesMapper#mapFromAttributes(javax.naming.directory.Attributes) + */ + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + Person person = new Person(); + person.setFullname((String) attributes.get("cn").get()); + person.setLastname((String) attributes.get("sn").get()); + person.setPhone((String) attributes.get("telephoneNumber").get()); + person.setDescription((String) attributes.get("description").get()); + return person; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/PersonContextMapper.java b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/PersonContextMapper.java new file mode 100644 index 00000000..da560d01 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/PersonContextMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import org.springframework.ldap.core.DirContextOperations; +import org.springframework.ldap.core.support.AbstractContextMapper; + +/** + * Dummy implemention of ContextMapper for use in the integration tests and for + * illustration purposes. + * + * @author Mattias Arthursson + */ +public class PersonContextMapper extends AbstractContextMapper { + + protected Object doMapFromContext(DirContextOperations ctx) { + Person person = new Person(); + person.setFullname(ctx.getStringAttribute("cn")); + person.setLastname(ctx.getStringAttribute("sn")); + person.setPhone(ctx.getStringAttribute("telephoneNumber")); + person.setDescription(ctx.getStringAttribute("description")); + + return person; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyDao.java b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyDao.java new file mode 100644 index 00000000..7d6f0e78 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyDao.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +public interface DummyDao { + void createWithException(String country, String company, String fullname, + String lastname, String description); + + void create(String country, String company, String fullname, + String lastname, String description); + + void update(String dn, String fullname, String lastname, String description); + + void updateWithException(String dn, String fullname, String lastname, + String description); + + void updateAndRename(String dn, String newDn, String description); + + void updateAndRenameWithException(String dn, String newDn, + String description); + + void modifyAttributes(String dn, String lastName, String description); + + void modifyAttributesWithException(String dn, String lastName, + String description); + + void unbind(String dn, String fullname); + + void unbindWithException(String dn, String fullname); + +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyException.java b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyException.java new file mode 100644 index 00000000..dacb7839 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyException.java @@ -0,0 +1,22 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +public class DummyException extends RuntimeException { + public DummyException(String message) { + super(message); + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyServiceImpl.java b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyServiceImpl.java new file mode 100644 index 00000000..f023fea5 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/DummyServiceImpl.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +public class DummyServiceImpl { + private DummyDao dummyDaoImpl; + + public void setDummyDaoImpl(DummyDao dummyDaoImpl) { + this.dummyDaoImpl = dummyDaoImpl; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/LdapAndJdbcDummyDaoImpl.java b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/LdapAndJdbcDummyDaoImpl.java new file mode 100644 index 00000000..ec0b43e6 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/LdapAndJdbcDummyDaoImpl.java @@ -0,0 +1,178 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; + +public class LdapAndJdbcDummyDaoImpl implements DummyDao { + private LdapTemplate ldapTemplate; + + private JdbcTemplate jdbcTemplate; + + public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#createWithException(java.lang.String, + * java.lang.String, java.lang.String, java.lang.String, + * java.lang.String) + */ + public void createWithException(String country, String company, + String fullname, String lastname, String description) { + create(country, company, fullname, lastname, description); + throw new DummyException("This method failed"); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#create(java.lang.String, + * java.lang.String, java.lang.String, java.lang.String, + * java.lang.String) + */ + public void create(String country, String company, String fullname, + String lastname, String description) { + DistinguishedName dn = new DistinguishedName(); + dn.add("c", country); + dn.add("ou", company); + dn.add("cn", fullname); + + DirContextAdapter ctx = new DirContextAdapter(); + ctx.setAttributeValues("objectclass", new String[] { "top", "person" }); + ctx.setAttributeValue("cn", fullname); + ctx.setAttributeValue("sn", lastname); + ctx.setAttributeValue("description", description); + ldapTemplate.bind(dn, ctx, null); + jdbcTemplate.update("insert into PERSON values(?, ?, ?)", new Object[] { + fullname, lastname, description }); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#update(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void update(String dn, String fullname, String lastname, + String description) { + DirContextAdapter ctx = (DirContextAdapter) ldapTemplate.lookup(dn); + ctx.setAttributeValue("sn", lastname); + ctx.setAttributeValue("description", description); + ctx.update(); + + ldapTemplate.rebind(dn, ctx, null); + jdbcTemplate + .update( + "update PERSON set lastname=?, description = ? where fullname = ?", + new Object[] { lastname, description, fullname }); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#updateWithException(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void updateWithException(String dn, String fullname, + String lastname, String description) { + update(dn, fullname, lastname, description); + throw new DummyException("This method failed."); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#updateAndRename(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void updateAndRename(String dn, String newDn, String description) { + DirContextAdapter ctx = (DirContextAdapter) ldapTemplate.lookup(dn); + ctx.setAttributeValue("description", description); + ctx.update(); + + ldapTemplate.rebind(dn, ctx, null); + ldapTemplate.rename(dn, newDn); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#updateAndRenameWithException(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void updateAndRenameWithException(String dn, String newDn, + String description) { + updateAndRename(dn, newDn, description); + throw new DummyException("This method failed."); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#modifyAttributes(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void modifyAttributes(String dn, String lastName, String description) { + DirContextAdapter ctx = (DirContextAdapter) ldapTemplate.lookup(dn); + ctx.setAttributeValue("sn", lastName); + ctx.setAttributeValue("description", description); + + ldapTemplate.modifyAttributes(dn, ctx.getModificationItems()); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#modifyAttributesWithException(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void modifyAttributesWithException(String dn, String lastName, + String description) { + modifyAttributes(dn, lastName, description); + throw new DummyException("This method failed."); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#unbind(java.lang.String) + */ + public void unbind(String dn, String fullname) { + ldapTemplate.unbind(dn); + jdbcTemplate.update("delete from PERSON where fullname=?", + new Object[] { fullname }); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#unbindWithException(java.lang.String) + */ + public void unbindWithException(String dn, String fullname) { + unbind(dn, fullname); + throw new DummyException("This operation failed."); + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/LdapDummyDaoImpl.java b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/LdapDummyDaoImpl.java new file mode 100644 index 00000000..4db82c0a --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/main/java/org/springframework/ldap/transaction/compensating/manager/LdapDummyDaoImpl.java @@ -0,0 +1,163 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; + +public class LdapDummyDaoImpl implements DummyDao { + private LdapTemplate ldapTemplate; + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#createWithException(java.lang.String, + * java.lang.String, java.lang.String, java.lang.String, + * java.lang.String) + */ + public void createWithException(String country, String company, + String fullname, String lastname, String description) { + create(country, company, fullname, lastname, description); + throw new DummyException("This method failed"); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#create(java.lang.String, + * java.lang.String, java.lang.String, java.lang.String, + * java.lang.String) + */ + public void create(String country, String company, String fullname, + String lastname, String description) { + DistinguishedName dn = new DistinguishedName(); + dn.add("c", country); + dn.add("ou", company); + dn.add("cn", fullname); + + DirContextAdapter ctx = new DirContextAdapter(); + ctx.setAttributeValues("objectclass", new String[] { "top", "person" }); + ctx.setAttributeValue("cn", fullname); + ctx.setAttributeValue("sn", lastname); + ctx.setAttributeValue("description", description); + ldapTemplate.bind(dn, ctx, null); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#update(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void update(String dn, String fullname, String lastname, + String description) { + DirContextAdapter ctx = (DirContextAdapter) ldapTemplate.lookup(dn); + ctx.setAttributeValue("sn", lastname); + ctx.setAttributeValue("description", description); + ctx.update(); + + ldapTemplate.rebind(dn, ctx, null); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#updateWithException(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void updateWithException(String dn, String fullname, + String lastname, String description) { + update(dn, fullname, lastname, description); + throw new DummyException("This method failed."); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#updateAndRename(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void updateAndRename(String dn, String newDn, String description) { + DirContextAdapter ctx = (DirContextAdapter) ldapTemplate.lookup(dn); + ctx.setAttributeValue("description", description); + ctx.update(); + + ldapTemplate.rebind(dn, ctx, null); + ldapTemplate.rename(dn, newDn); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#updateAndRenameWithException(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void updateAndRenameWithException(String dn, String newDn, + String description) { + updateAndRename(dn, newDn, description); + throw new DummyException("This method failed."); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#modifyAttributes(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void modifyAttributes(String dn, String lastName, String description) { + DirContextAdapter ctx = (DirContextAdapter) ldapTemplate.lookup(dn); + ctx.setAttributeValue("sn", lastName); + ctx.setAttributeValue("description", description); + + ldapTemplate.modifyAttributes(dn, ctx.getModificationItems()); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#modifyAttributesWithException(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void modifyAttributesWithException(String dn, String lastName, + String description) { + modifyAttributes(dn, lastName, description); + throw new DummyException("This method failed."); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#unbind(java.lang.String) + */ + public void unbind(String dn, String fullname) { + ldapTemplate.unbind(dn); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.transaction.support.DummyDao#unbindWithException(java.lang.String) + */ + public void unbindWithException(String dn, String fullname) { + unbind(dn, fullname); + throw new DummyException("This operation failed."); + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/log4j.properties b/mvn-build/integration-tests/core-integration-tests/src/test/java/log4j.properties new file mode 100644 index 00000000..f57302e8 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/log4j.properties @@ -0,0 +1,9 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +#Enable debug logging +log4j.category.net.sf.ldaptemplate=INFO +log4j.category.net.sf.ldaptemplate.LdapTemplate=DEBUG diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/AbstractLdapTemplateIntegrationTest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/AbstractLdapTemplateIntegrationTest.java new file mode 100644 index 00000000..e09c0e94 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/AbstractLdapTemplateIntegrationTest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap; + +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +public abstract class AbstractLdapTemplateIntegrationTest extends + AbstractDependencyInjectionSpringContextTests { + + + protected void onSetUp() throws Exception { + super.onSetUp(); + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAttributesMapperITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAttributesMapperITest.java new file mode 100644 index 00000000..8acd316f --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAttributesMapperITest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import java.util.LinkedList; +import java.util.List; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; + +/** + * Tests the attributes mapper search method. + * + * @author Mattias Arthursson + */ +public class LdapTemplateAttributesMapperITest extends + AbstractLdapTemplateIntegrationTest { + private LdapTemplate tested; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + public void testSearch_AttributeMapper() throws Exception { + AttributesMapper mapper = new PersonAttributesMapper(); + List result = tested.search("ou=company1,c=Sweden", + "(&(objectclass=person)(sn=Person2))", mapper); + + assertEquals(1, result.size()); + Person person = (Person) result.get(0); + assertEquals("Some Person2", person.getFullname()); + assertEquals("Person2", person.getLastname()); + assertEquals("Sweden, Company1, Some Person2", person.getDescription()); + } + + /** + * Demonstrates how to retrieve all values of a multi-value attribute. + * + * @see LdapTemplateContextMapperITest#testSearch_ContextMapper_MultiValue() + */ + public void testSearch_AttributesMapper_MultiValue() throws Exception { + AttributesMapper mapper = new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) throws NamingException { + LinkedList list = new LinkedList(); + NamingEnumeration enumeration = attributes.get("uniqueMember").getAll(); + while (enumeration.hasMoreElements()) { + String value = (String) enumeration.nextElement(); + list.add(value); + } + String[] members = (String[]) list.toArray(new String[0]); + return members; + } + }; + List result = tested.search("ou=groups", + "(objectclass=groupOfUniqueNames)", mapper); + + assertEquals(2, result.size()); + assertEquals(1, ((String[]) result.get(0)).length); + assertEquals(5, ((String[]) result.get(1)).length); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateBindUnbindITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateBindUnbindITest.java new file mode 100644 index 00000000..42297c48 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateBindUnbindITest.java @@ -0,0 +1,119 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; + +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; + +/** + * Tests the bind and unbind methods of LdapTemplate. The test methods in this + * class tests a little too much, but we need to clean up after binding, so the + * most efficient way to test is to do it all in one test method. Also, the + * methods in this class relies on that the lookup method works as it should - + * that should be ok, since that is verified in a separate test class. NOTE: if + * any of the tests in this class fails, it may be necessary to run the cleanup + * script as described in README.txt under /src/iutest/. + * + * @author Mattias Arthursson + */ +public class LdapTemplateBindUnbindITest extends + AbstractLdapTemplateIntegrationTest { + private LdapTemplate tested; + + private static String DN = "cn=Some Person4,ou=company1,c=Sweden"; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + public void testBindAndUnbind_Attributes_Plain() { + Attributes attributes = setupAttributes(); + tested.bind(DN, null, attributes); + verifyBoundCorrectData(); + tested.unbind(DN); + verifyCleanup(); + } + + public void testBindAndUnbind_Attributes_DIstinguishedName() { + Attributes attributes = setupAttributes(); + tested.bind(new DistinguishedName(DN), null, attributes); + verifyBoundCorrectData(); + tested.unbind(new DistinguishedName(DN)); + verifyCleanup(); + } + + public void testBindAndUnbind_DirContextAdapter_Plain() { + DirContextAdapter adapter = new DirContextAdapter(); + adapter.setAttributeValues("objectclass", new String[] { "top", + "person" }); + adapter.setAttributeValue("cn", "Some Person4"); + adapter.setAttributeValue("sn", "Person4"); + + tested.bind(DN, adapter, null); + verifyBoundCorrectData(); + tested.unbind(DN); + verifyCleanup(); + } + + public void testBindAndUnbind_DirContextAdapter_DIstinguishedName() { + DirContextAdapter adapter = new DirContextAdapter(); + adapter.setAttributeValues("objectclass", new String[] { "top", + "person" }); + adapter.setAttributeValue("cn", "Some Person4"); + adapter.setAttributeValue("sn", "Person4"); + + tested.bind(new DistinguishedName(DN), adapter, null); + verifyBoundCorrectData(); + tested.unbind(new DistinguishedName(DN)); + verifyCleanup(); + } + + private Attributes setupAttributes() { + Attributes attributes = new BasicAttributes(); + BasicAttribute ocattr = new BasicAttribute("objectclass"); + ocattr.add("top"); + ocattr.add("person"); + attributes.put(ocattr); + attributes.put("cn", "Some Person4"); + attributes.put("sn", "Person4"); + return attributes; + } + + private void verifyBoundCorrectData() { + DirContextAdapter result = (DirContextAdapter) tested.lookup(DN); + assertEquals("Some Person4", result.getStringAttribute("cn")); + assertEquals("Person4", result.getStringAttribute("sn")); + } + + private void verifyCleanup() { + try { + tested.lookup(DN); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateContextExecutorTest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateContextExecutorTest.java new file mode 100644 index 00000000..8561d09d --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateContextExecutorTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import org.springframework.ldap.core.ContextExecutor; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.LdapTemplate; + +/** + * Tests for LdapTemplate's context executor methods. + * + * @author Mattias Arthursson + */ +public class LdapTemplateContextExecutorTest extends + AbstractLdapTemplateIntegrationTest { + + private LdapTemplate tested; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + protected void onSetUp() throws Exception { + super.onSetUp(); + } + + protected void onTearDown() throws Exception { + super.onTearDown(); + } + + public void testLookupLink() { + ContextExecutor executor = new ContextExecutor(){ + public Object executeWithContext(DirContext ctx) throws NamingException { + return ctx.lookupLink("cn=Some Person,ou=company1,c=Sweden"); + } + }; + + Object object = tested.executeReadOnly(executor); + assertTrue("Should be a DirContextAdapter", object instanceof DirContextAdapter); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateContextMapperITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateContextMapperITest.java new file mode 100644 index 00000000..d2ca77b2 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateContextMapperITest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import java.util.List; + +import org.springframework.ldap.core.ContextMapper; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.LdapTemplate; + +/** + * Tests the ContextMapper search method. In its way this method also + * demonstrates the use of DirContextAdapter and the DirObjectFactory. + * + * @author Mattias Arthursson + */ +public class LdapTemplateContextMapperITest extends + AbstractLdapTemplateIntegrationTest { + private LdapTemplate tested; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + /** + * This method depends on a DirObjectFactory ({@link org.springframework.ldap.core.support.DefaultDirObjectFactory}) + * being set in the ContextSource. + */ + public void testSearch_ContextMapper() { + ContextMapper mapper = new PersonContextMapper(); + List result = tested.search("ou=company1,c=Sweden", + "(&(objectclass=person)(sn=Person2))", mapper); + + assertEquals(1, result.size()); + Person person = (Person) result.get(0); + assertEquals("Some Person2", person.getFullname()); + assertEquals("Person2", person.getLastname()); + assertEquals("Sweden, Company1, Some Person2", person.getDescription()); + } + + /** + * Demonstrates how to retrieve all values of a multi-value attribute. + * + * @see LdapTemplateAttributesMapperITest#testSearch_AttributesMapper_MultiValue() + */ + public void testSearch_ContextMapper_MultiValue() throws Exception { + ContextMapper mapper = new ContextMapper() { + public Object mapFromContext(Object ctx) { + DirContextAdapter adapter = (DirContextAdapter) ctx; + String[] members = adapter.getStringAttributes("uniqueMember"); + return members; + } + }; + List result = tested.search("ou=groups", + "(objectclass=groupOfUniqueNames)", mapper); + + assertEquals(2, result.size()); + assertEquals(1, ((String[]) result.get(0)).length); + assertEquals(5, ((String[]) result.get(1)).length); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateListITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateListITest.java new file mode 100644 index 00000000..58d0e27c --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateListITest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap; + +import java.util.List; + +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.support.CountNameClassPairCallbackHandler; +import org.springframework.ldap.test.AttributeCheckContextMapper; + +/** + * Tests for LdapTemplate's list methods. + * + * @author Ulrik Sandberg + */ +public class LdapTemplateListITest extends + AbstractLdapTemplateIntegrationTest { + + private LdapTemplate tested; + + private AttributeCheckContextMapper contextMapper; + + private static final String BASE_STRING = ""; + + private static final DistinguishedName BASE_NAME = new DistinguishedName( + BASE_STRING); + + private static final String[] ALL_ATTRIBUTES = { "cn", "sn", "description", + "telephoneNumber" }; + + private static final String[] ALL_VALUES = { "Some Person", "Person", + "Sweden, Company2, Some Person", "+46 555-456321" }; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + protected void onSetUp() throws Exception { + super.onSetUp(); + + contextMapper = new AttributeCheckContextMapper(); + } + + protected void onTearDown() throws Exception { + super.onTearDown(); + + contextMapper = null; + } + + public void testListBindings_ContextMapper() { + contextMapper.setExpectedAttributes(ALL_ATTRIBUTES); + contextMapper.setExpectedValues(ALL_VALUES); + List list = tested.listBindings("ou=company2,c=Sweden" + BASE_STRING, + contextMapper); + assertEquals(1, list.size()); + } + + public void testListBindings_ContextMapper_Name() { + contextMapper.setExpectedAttributes(ALL_ATTRIBUTES); + contextMapper.setExpectedValues(ALL_VALUES); + DistinguishedName dn = new DistinguishedName("ou=company2,c=Sweden"); + dn.append(BASE_NAME); + List list = tested.listBindings(dn, contextMapper); + assertEquals(1, list.size()); + } + + public void testListBindings_ContextMapper_MapToPersons() { + DistinguishedName dn = new DistinguishedName("ou=company1,c=Sweden"); + dn.append(BASE_NAME); + List list = tested.listBindings(dn, new PersonContextMapper()); + assertEquals(3, list.size()); + String personClass = "org.springframework.ldap.Person"; + assertEquals(personClass, list.get(0).getClass().getName()); + assertEquals(personClass, list.get(1).getClass().getName()); + assertEquals(personClass, list.get(2).getClass().getName()); + } + + public void testList() { + List list = tested.list(BASE_STRING); + assertEquals(3, list.size()); + assertTrue(list.contains("ou=groups")); + assertTrue(list.contains("c=Norway")); + assertTrue(list.contains("c=Sweden")); + } + + public void testList_Name() { + List list = tested.list(BASE_NAME); + assertEquals(3, list.size()); + assertTrue(list.contains("ou=groups")); + assertTrue(list.contains("c=Norway")); + assertTrue(list.contains("c=Sweden")); + } + + public void testList_Handler() throws Exception { + CountNameClassPairCallbackHandler handler = new CountNameClassPairCallbackHandler(); + tested.list(BASE_STRING, handler); + assertEquals(3, handler.getNoOfRows()); + } + + public void testList_Name_Handler() throws Exception { + CountNameClassPairCallbackHandler handler = new CountNameClassPairCallbackHandler(); + tested.list(BASE_NAME, handler); + assertEquals(3, handler.getNoOfRows()); + } + + public void testListBindings() { + List list = tested.listBindings(BASE_STRING); + assertEquals(3, list.size()); + assertTrue(list.contains("ou=groups")); + assertTrue(list.contains("c=Norway")); + assertTrue(list.contains("c=Sweden")); + } + + public void testListBindings_Name() { + List list = tested.listBindings(BASE_NAME); + assertEquals(3, list.size()); + } + + public void testListBindings_Handler() throws Exception { + CountNameClassPairCallbackHandler handler = new CountNameClassPairCallbackHandler(); + tested.listBindings(BASE_STRING, handler); + assertEquals(3, handler.getNoOfRows()); + } + + public void testListBindings_Name_Handler() throws Exception { + CountNameClassPairCallbackHandler handler = new CountNameClassPairCallbackHandler(); + tested.listBindings(BASE_NAME, handler); + assertEquals(3, handler.getNoOfRows()); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateLookupITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateLookupITest.java new file mode 100644 index 00000000..cb479a9b --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateLookupITest.java @@ -0,0 +1,245 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.ContextMapper; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.support.AbstractContextSource; + +/** + * Tests the lookup methods of LdapTemplate. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public class LdapTemplateLookupITest extends + AbstractLdapTemplateIntegrationTest { + + private LdapTemplate tested; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + /** + * This method depends on a DirObjectFactory ({@link org.springframework.ldap.core.support.DefaultDirObjectFactory}) + * being set in the ContextSource. + */ + public void testLookup_Plain() { + DirContextAdapter result = (DirContextAdapter) tested + .lookup("cn=Some Person2, ou=company1,c=Sweden"); + + assertEquals("Some Person2", result.getStringAttribute("cn")); + assertEquals("Person2", result.getStringAttribute("sn")); + assertEquals("Sweden, Company1, Some Person2", result + .getStringAttribute("description")); + } + + public void testLookup_AttributesMapper() { + AttributesMapper mapper = new PersonAttributesMapper(); + Person person = (Person) tested.lookup( + "cn=Some Person2, ou=company1,c=Sweden", mapper); + + assertEquals("Some Person2", person.getFullname()); + assertEquals("Person2", person.getLastname()); + assertEquals("Sweden, Company1, Some Person2", person.getDescription()); + } + + public void testLookup_AttributesMapper_DistinguishedName() { + AttributesMapper mapper = new PersonAttributesMapper(); + Person person = (Person) tested.lookup(new DistinguishedName( + "cn=Some Person2, ou=company1,c=Sweden"), mapper); + + assertEquals("Some Person2", person.getFullname()); + assertEquals("Person2", person.getLastname()); + assertEquals("Sweden, Company1, Some Person2", person.getDescription()); + } + + /** + * An {@link AttributesMapper} that only maps a subset of the full + * attributes list. Used in tests where the return attributes list has been + * limited. + * + * @author Ulrik Sandberg + */ + private final class SubsetPersonAttributesMapper implements + AttributesMapper { + /** + * Maps the cn attribute into a {@link Person} object. + * Also verifies that the other attributes haven't been set. + * + * @see org.springframework.ldap.core.AttributesMapper#mapFromAttributes(javax.naming.directory.Attributes) + */ + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + Person person = new Person(); + person.setFullname((String) attributes.get("cn").get()); + assertNull("sn should be null", attributes.get("sn")); + assertNull("description should be null", attributes + .get("description")); + return person; + } + } + + /** + * Verifies that only the subset is used when specifying a subset of the + * available attributes as return attributes. + */ + public void testLookup_ReturnAttributes_AttributesMapper() { + AttributesMapper mapper = new SubsetPersonAttributesMapper(); + + Person person = (Person) tested.lookup( + "cn=Some Person2, ou=company1,c=Sweden", new String[] { "cn" }, + mapper); + + assertEquals("Some Person2", person.getFullname()); + assertNull("lastName should not be set", person.getLastname()); + assertNull("description should not be set", person.getDescription()); + } + + /** + * Verifies that only the subset is used when specifying a subset of the + * available attributes as return attributes. Uses DistinguishedName instead + * of plain string as name. + */ + public void testLookup_ReturnAttributes_AttributesMapper_DistinguishedName() { + AttributesMapper mapper = new SubsetPersonAttributesMapper(); + Person person = (Person) tested.lookup(new DistinguishedName( + "cn=Some Person2, ou=company1,c=Sweden"), + new String[] { "cn" }, mapper); + + assertEquals("Some Person2", person.getFullname()); + assertNull("lastName should not be set", person.getLastname()); + assertNull("description should not be set", person.getDescription()); + } + + /** + * This method depends on a DirObjectFactory ({@link org.springframework.ldap.core.support.DefaultDirObjectFactory}) + * being set in the ContextSource. + */ + public void testLookup_ContextMapper() { + ContextMapper mapper = new PersonContextMapper(); + Person person = (Person) tested.lookup( + "cn=Some Person2, ou=company1,c=Sweden", mapper); + + assertEquals("Some Person2", person.getFullname()); + assertEquals("Person2", person.getLastname()); + assertEquals("Sweden, Company1, Some Person2", person.getDescription()); + } + + /** + * Verifies that only the subset is used when specifying a subset of the + * available attributes as return attributes. + */ + public void testLookup_ReturnAttributes_ContextMapper() { + ContextMapper mapper = new PersonContextMapper(); + + Person person = (Person) tested.lookup( + "cn=Some Person2, ou=company1,c=Sweden", new String[] { "cn" }, + mapper); + + assertEquals("Some Person2", person.getFullname()); + assertNull("lastName should not be set", person.getLastname()); + assertNull("description should not be set", person.getDescription()); + } + + /** + * Verifies that we can lookup an entry that has a multi-valued rdn, which + * means more than one attribute is part of the relative DN for the entry. + * + * TODO Enable test when ApacheDS supports multi-valued rdns. + */ + public void DISABLED_testLookup_MultiValuedRdn() { + AttributesMapper mapper = new PersonAttributesMapper(); + Person person = (Person) tested.lookup( + "cn=Some Person+sn=Person, ou=company1,c=Norway", mapper); + + assertEquals("Some Person", person.getFullname()); + assertEquals("Person", person.getLastname()); + assertEquals("Norway, Company1, Some Person2", person.getDescription()); + } + + /** + * Verifies that we can lookup an entry that has a multi-valued rdn, which + * means more than one attribute is part of the relative DN for the entry. + * + * TODO Enable test when ApacheDS supports multi-valued rdns. + */ + public void DISABLED_testLookup_MultiValuedRdn_DirContextAdapter() { + DirContextAdapter result = (DirContextAdapter) tested + .lookup("cn=Some Person+sn=Person, ou=company1,c=Norway"); + + assertEquals("Some Person", result.getStringAttribute("cn")); + assertEquals("Person", result.getStringAttribute("sn")); + assertEquals("Norway, Company1, Some Person", result + .getStringAttribute("description")); + } + + public void testLookup_GetNameInNamespace_Plain() { + DirContextAdapter result = (DirContextAdapter) tested + .lookup("cn=Some Person2, ou=company1,c=Sweden"); + + assertEquals("cn=Some Person2, ou=company1, c=Sweden", result.getDn() + .toString()); + assertEquals( + "cn=Some Person2, ou=company1, c=Sweden, dc=jayway, dc=se", + result.getNameInNamespace()); + } + + public void testLookup_GetNameInNamespace_MultiRdn() { + DirContextAdapter result = (DirContextAdapter) tested + .lookup("cn=Some Person+sn=Person, ou=company1,c=Norway"); + + assertEquals("cn=Some Person+sn=Person, ou=company1, c=Norway", result + .getDn().toString()); + assertEquals( + "cn=Some Person+sn=Person, ou=company1, c=Norway, dc=jayway, dc=se", + result.getNameInNamespace()); + } + + /** + * Tests bind and lookup with Java objects where the ContextSource already + * has a DirObjectFactory configured. + */ + public void testBindJavaObject() throws Exception { + AbstractContextSource contextSource = (AbstractContextSource) tested + .getContextSource(); + Class originalObjectFactory = contextSource.getDirObjectFactory(); + try { + contextSource.setDirObjectFactory(null); + contextSource.afterPropertiesSet(); + tested.bind("cn=myRandomInt", new Integer(54321), null); + Integer result = (Integer) tested.lookup("cn=myRandomInt"); + assertEquals(54321, result.intValue()); + } finally { + // reset the DirObjectFactory so as not to disturb other tests + tested.unbind("cn=myRandomInt"); + contextSource.setDirObjectFactory(originalObjectFactory); + contextSource.afterPropertiesSet(); + } + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateModifyITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateModifyITest.java new file mode 100644 index 00000000..bf5560c6 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateModifyITest.java @@ -0,0 +1,253 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; + +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; + +/** + * Tests the modification methods (rebind and modifyAttributes) of LdapTemplate. + * It also illustrates the use of DirContextAdapter as a means of getting + * ModificationItems, in order to avoid doing a full rebind and use + * modifyAttributes() instead. We rely on that the bind, unbind and lookup + * methods work as they should - that should be ok, since that is verified in a + * separate test class. NOTE: if any of the tests in this class fails, it may be + * necessary to run the cleanup script as described in README.txt under + * /src/iutest/. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public class LdapTemplateModifyITest extends + AbstractLdapTemplateIntegrationTest { + private LdapTemplate tested; + + private static String PERSON4_DN = "cn=Some Person4,ou=company1,c=Sweden"; + + private static String PERSON5_DN = "cn=Some Person5,ou=company1,c=Sweden"; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + protected void onSetUp() throws Exception { + super.onSetUp(); + + DirContextAdapter adapter = new DirContextAdapter(); + adapter.setAttributeValues("objectclass", new String[] { "top", + "person" }); + adapter.setAttributeValue("cn", "Some Person4"); + adapter.setAttributeValue("sn", "Person4"); + adapter.setAttributeValue("description", "Some description"); + + tested.bind(PERSON4_DN, adapter, null); + + adapter = new DirContextAdapter(); + adapter.setAttributeValues("objectclass", new String[] { "top", + "person" }); + adapter.setAttributeValue("cn", "Some Person5"); + adapter.setAttributeValue("sn", "Person5"); + adapter.setAttributeValues("description", new String[] { "qwe", "123", + "rty", "uio" }); + + tested.bind(PERSON5_DN, adapter, null); + + } + + protected void onTearDown() throws Exception { + tested.unbind(PERSON4_DN); + tested.unbind(PERSON5_DN); + } + + public void testRebind_Attributes_Plain() { + Attributes attributes = setupAttributes(); + + tested.rebind(PERSON4_DN, null, attributes); + + verifyBoundCorrectData(); + } + + public void testRebind_Attributes_DistinguishedName() { + Attributes attributes = setupAttributes(); + + tested.rebind(new DistinguishedName(PERSON4_DN), null, attributes); + + verifyBoundCorrectData(); + } + + public void testModifyAttributes_MultiValueReplace() { + BasicAttribute attr = new BasicAttribute("description", + "Some other description"); + attr.add("Another description"); + ModificationItem[] mods = new ModificationItem[1]; + mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr); + + tested.modifyAttributes(PERSON4_DN, mods); + + DirContextAdapter result = (DirContextAdapter) tested + .lookup(PERSON4_DN); + String[] attributes = result.getStringAttributes("description"); + assertEquals(2, attributes.length); + assertEquals("Some other description", attributes[0]); + assertEquals("Another description", attributes[1]); + } + + public void testModifyAttributes_MultiValueAdd() { + BasicAttribute attr = new BasicAttribute("description", + "Some other description"); + attr.add("Another description"); + ModificationItem[] mods = new ModificationItem[1]; + mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr); + + tested.modifyAttributes(PERSON4_DN, mods); + + DirContextAdapter result = (DirContextAdapter) tested + .lookup(PERSON4_DN); + String[] attributes = result.getStringAttributes("description"); + assertEquals(3, attributes.length); + assertEquals("Some description", attributes[0]); + assertEquals("Some other description", attributes[1]); + assertEquals("Another description", attributes[2]); + } + + public void testModifyAttributes_MultiValueAddDuplicateToUnordered() { + BasicAttribute attr = new BasicAttribute("description", + "Some description"); + ModificationItem[] mods = new ModificationItem[1]; + mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr); + + try { + tested.modifyAttributes(PERSON4_DN, mods); + fail("AttributeInUseException expected"); + } catch (AttributeInUseException expected) { + // expected + } + } + + /** + * Test written originally to verify that duplicates are allowed on ordered + * attributes, but had to be changed since Apache DS seems to disallow + * duplicates even for ordered attributes. + */ + public void testModifyAttributes_MultiValueAddDuplicateToOrdered() { + BasicAttribute attr = new BasicAttribute("description", + "Some other description", true); // ordered + attr.add("Another description"); + // Commented out duplicate to make test work for Apache DS + // attr.add("Some description"); + ModificationItem[] mods = new ModificationItem[1]; + mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr); + + tested.modifyAttributes(PERSON4_DN, mods); + + DirContextAdapter result = (DirContextAdapter) tested + .lookup(PERSON4_DN); + String[] attributes = result.getStringAttributes("description"); + assertEquals(3, attributes.length); + assertEquals("Some description", attributes[0]); + assertEquals("Some other description", attributes[1]); + assertEquals("Another description", attributes[2]); + } + + public void testModifyAttributes_Plain() { + ModificationItem item = new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("description", + "Some other description")); + + tested.modifyAttributes(PERSON4_DN, new ModificationItem[] { item }); + + verifyBoundCorrectData(); + } + + public void testModifyAttributes_DistinguishedName() { + ModificationItem item = new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("description", + "Some other description")); + + tested.modifyAttributes(new DistinguishedName(PERSON4_DN), + new ModificationItem[] { item }); + + verifyBoundCorrectData(); + } + + public void testModifyAttributes_DirContextAdapter_MultiAttributes() { + DirContextAdapter adapter = (DirContextAdapter) tested + .lookup(PERSON5_DN); + adapter.setAttributeValues("description", new String[] { "qwe", "123", + "klytt", "kalle" }); + + tested.modifyAttributes(PERSON5_DN, adapter.getModificationItems()); + + // Verify + adapter = (DirContextAdapter) tested.lookup(PERSON5_DN); + String[] attributes = adapter.getStringAttributes("description"); + assertEquals(4, attributes.length); + assertEquals("qwe", attributes[0]); + assertEquals("123", attributes[1]); + assertEquals("klytt", attributes[2]); + assertEquals("kalle", attributes[3]); + } + + /** + * Demonstrates how the DirContextAdapter can be used to automatically keep + * track of changes of the attributes and deliver ModificationItems to use + * in moifyAttributes(). + */ + public void testModifyAttributes_DirContextAdapter() throws Exception { + DirContextAdapter adapter = (DirContextAdapter) tested + .lookup(PERSON4_DN); + + adapter.setAttributeValue("description", "Some other description"); + + ModificationItem[] modificationItems = adapter.getModificationItems(); + tested.modifyAttributes(PERSON4_DN, modificationItems); + + verifyBoundCorrectData(); + } + + private Attributes setupAttributes() { + Attributes attributes = new BasicAttributes(); + BasicAttribute ocattr = new BasicAttribute("objectclass"); + ocattr.add("top"); + ocattr.add("person"); + attributes.put(ocattr); + attributes.put("cn", "Some Person4"); + attributes.put("sn", "Person4"); + attributes.put("description", "Some other description"); + return attributes; + } + + private void verifyBoundCorrectData() { + DirContextAdapter result = (DirContextAdapter) tested + .lookup(PERSON4_DN); + assertEquals("Some Person4", result.getStringAttribute("cn")); + assertEquals("Person4", result.getStringAttribute("sn")); + assertEquals("Some other description", result + .getStringAttribute("description")); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateNoBaseSuffixITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateNoBaseSuffixITest.java new file mode 100644 index 00000000..f82a2e6d --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateNoBaseSuffixITest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.support.CountNameClassPairCallbackHandler; + +/** + * Tests to verify that not setting a base suffix on the ContextSource (as + * defined in ldapTemplateNoBaseSuffixTestContext.xml) works as expected. + * + * NOTE: This test will not work under Java 1.4.1 or earlier. + * + * @author Mattias Arthursson + */ +public class LdapTemplateNoBaseSuffixITest extends + AbstractLdapTemplateIntegrationTest { + private LdapTemplate tested; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateNoBaseSuffixTestContext.xml" }; + } + + /** + * This method depends on a DirObjectFactory ({@link org.springframework.ldap.core.support.DefaultDirObjectFactory}) + * being set in the ContextSource. + */ + public void testLookup_Plain() { + DirContextAdapter result = (DirContextAdapter) tested + .lookup("cn=Some Person2, ou=company1, c=Sweden, dc=jayway, dc=se"); + + assertEquals("Some Person2", result.getStringAttribute("cn")); + assertEquals("Person2", result.getStringAttribute("sn")); + assertEquals("Sweden, Company1, Some Person2", result + .getStringAttribute("description")); + assertEquals( + "cn=Some Person2, ou=company1, c=Sweden, dc=jayway, dc=se", + result.getDn().toString()); + assertEquals( + "cn=Some Person2, ou=company1, c=Sweden, dc=jayway, dc=se", + result.getNameInNamespace()); + } + + public void testSearch_Plain() { + CountNameClassPairCallbackHandler handler = new CountNameClassPairCallbackHandler(); + + tested.search("dc=jayway, dc=se", "(objectclass=person)", handler); + assertEquals(5, handler.getNoOfRows()); + } + + public void testBindAndUnbind_Plain() { + DirContextAdapter adapter = new DirContextAdapter(); + adapter.setAttributeValues("objectclass", new String[] { "top", + "person" }); + adapter.setAttributeValue("cn", "Some Person4"); + adapter.setAttributeValue("sn", "Person4"); + tested.bind("cn=Some Person4, ou=company1, c=Sweden, dc=jayway, dc=se", + adapter, null); + + DirContextAdapter result = (DirContextAdapter) tested + .lookup("cn=Some Person4, ou=company1, c=Sweden, dc=jayway, dc=se"); + + assertEquals("Some Person4", result.getStringAttribute("cn")); + assertEquals("Person4", result.getStringAttribute("sn")); + assertEquals( + "cn=Some Person4, ou=company1, c=Sweden, dc=jayway, dc=se", + result.getDn().toString()); + + tested + .unbind("cn=Some Person4, ou=company1, c=Sweden, dc=jayway, dc=se"); + try { + tested + .lookup("cn=Some Person4, ou=company1, c=Sweden, dc=jayway, dc=se"); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateObjectBindIntegrationTest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateObjectBindIntegrationTest.java new file mode 100644 index 00000000..f4b92d3d --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateObjectBindIntegrationTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import java.io.Serializable; +import java.util.LinkedList; + +import javax.naming.directory.DirContext; + +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.support.AbstractContextSource; + +/** + * Tests bind and lookup with Java objects where the ContextSource has a + * null DirObjectFactory configured. + * + * @author Ulrik Sandberg + */ +public class LdapTemplateObjectBindIntegrationTest extends + AbstractLdapTemplateIntegrationTest { + + private LdapTemplate tested; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateObjectBindTestContext.xml" }; + } + + public void testBindJavaObject() throws Exception { + String dn = "cn=myRandomInt"; + tested.bind(dn, new Integer(54321), null); + + Integer result = (Integer) tested.lookup(dn); + assertEquals(54321, result.intValue()); + tested.unbind(dn); + } + + public void testBindLinkedList() { + LinkedList list = new LinkedList(); + list.add(new Integer(54321)); + list.add(new Integer(67890)); + + String dn = "cn=myRandomList"; + tested.bind(dn, list, null); + + LinkedList result = (LinkedList) tested.lookup(dn); + assertEquals(2, result.size()); + assertEquals(54321, ((Integer) result.get(0)).intValue()); + assertEquals(67890, ((Integer) result.get(1)).intValue()); + tested.unbind(dn); + } + + public void testBindNonSerializableJavaObjectShouldFail() throws Exception { + NonSerializablePojo pojo = new NonSerializablePojo(); + pojo.setName("A Name"); + + try { + tested.bind("cn=myRandomObject", pojo, null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertEquals( + "can only bind Referenceable, Serializable, DirContext", + expected.getMessage()); + } + } + + /** + * Custom non-serializable class used for demonstrating bind and lookup of + * Java objects. + */ + public static class NonSerializablePojo { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public void testBindSerializableJavaObjectShouldSucceed() throws Exception { + SerializablePojo pojo = new SerializablePojo(); + pojo.setName("A Name"); + + tested.bind("cn=myRandomObject", pojo, null); + + SerializablePojo result = (SerializablePojo) tested + .lookup("cn=myRandomObject"); + assertEquals("A Name", result.getName()); + tested.unbind("cn=myRandomObject"); + } + + /** + * Custom serializable class used for demonstrating bind and lookup of Java + * objects. + */ + public static class SerializablePojo implements Serializable { + private static final long serialVersionUID = 3655768927093734908L; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + /** + * This test demonstrates that it's fully possible to perform plain JNDI + * operations from Spring LDAP. + */ + public void testPlainJndiBindJavaObject() throws Exception { + AbstractContextSource contextSource = (AbstractContextSource) tested + .getContextSource(); + DirContext ctx = contextSource.getReadWriteContext(); + + ctx.bind("cn=myRandomInt", new Integer(28420)); + + Integer result = (Integer) ctx.lookup("cn=myRandomInt"); + assertEquals(28420, result.intValue()); + tested.unbind("cn=myRandomInt"); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateRecursiveDeleteITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateRecursiveDeleteITest.java new file mode 100644 index 00000000..e43e14aa --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateRecursiveDeleteITest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import javax.naming.Name; + +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; + +/** + * Tests the recursive modification methods (unbind and the protected delete + * methods) of LdapTemplate. + * + * @author Mattias Arthursson + * @author Ulrik Sandberg + */ +public class LdapTemplateRecursiveDeleteITest extends + AbstractLdapTemplateIntegrationTest { + private LdapTemplate tested; + + private static DistinguishedName DN = new DistinguishedName( + "cn=Some Person5,ou=company1,c=Sweden"); + + private DistinguishedName firstSubDn; + + private DistinguishedName secondSubDn; + + private DistinguishedName leafDn; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + protected void onSetUp() throws Exception { + super.onSetUp(); + + DirContextAdapter adapter = new DirContextAdapter(); + adapter.setAttributeValues("objectclass", new String[] { "top", + "person" }); + adapter.setAttributeValue("cn", "Some Person5"); + adapter.setAttributeValue("sn", "Person5"); + adapter.setAttributeValue("description", "Some description"); + tested.bind(DN, adapter, null); + + firstSubDn = new DistinguishedName("cn=subPerson"); + firstSubDn.prepend(DN); + adapter = new DirContextAdapter(); + adapter.setAttributeValues("objectclass", new String[] { "top", + "person" }); + adapter.setAttributeValue("cn", "subPerson"); + adapter.setAttributeValue("sn", "subPerson"); + adapter.setAttributeValue("description", + "Should be recursively deleted"); + tested.bind(firstSubDn, adapter, null); + secondSubDn = new DistinguishedName("cn=subPerson2"); + secondSubDn.prepend(DN); + adapter = new DirContextAdapter(); + adapter.setAttributeValues("objectclass", new String[] { "top", + "person" }); + adapter.setAttributeValue("cn", "subPerson2"); + adapter.setAttributeValue("sn", "subPerson2"); + adapter.setAttributeValue("description", + "Should be recursively deleted"); + tested.bind(secondSubDn, adapter, null); + + leafDn = new DistinguishedName("cn=subSubPerson"); + leafDn.prepend(firstSubDn); + adapter = new DirContextAdapter(); + adapter.setAttributeValues("objectclass", new String[] { "top", + "person" }); + adapter.setAttributeValue("cn", "subSubPerson"); + adapter.setAttributeValue("sn", "subSubPerson"); + adapter.setAttributeValue("description", + "Should be recursively deleted"); + tested.bind(leafDn, adapter, null); + } + + protected void onTearDown() throws Exception { + try { + tested.unbind(DN, true); + } catch (NameNotFoundException ignore) { + // ignore + } + } + + public void testRecursiveUnbind() { + tested.unbind(DN, true); + + verifyDeleted(DN); + verifyDeleted(firstSubDn); + verifyDeleted(secondSubDn); + verifyDeleted(leafDn); + } + + public void testRecursiveUnbindOnLeaf() { + tested.unbind(leafDn, true); + verifyDeleted(leafDn); + } + + private void verifyDeleted(Name dn) { + try { + tested.lookup(dn); + fail("Expected entry '" + dn + "' to be non-existent"); + } catch (NameNotFoundException expected) { + // expected + } + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateRenameITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateRenameITest.java new file mode 100644 index 00000000..64a78c51 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateRenameITest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap; + +import javax.naming.Name; + +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; + +/** + * Tests the rename methods of LdapTemplate. + * + * We rely on that the bind, unbind and lookup methods work as they should - + * that should be ok, since that is verified in a separate test class. * + * + * @author Ulrik Sandberg + */ +public class LdapTemplateRenameITest extends + AbstractLdapTemplateIntegrationTest { + private LdapTemplate tested; + + private static String DN = "cn=Some Person6,ou=company1,c=Sweden"; + + private static String NEWDN = "cn=Some Person6,ou=company2,c=Sweden"; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + protected void onSetUp() throws Exception { + super.onSetUp(); + + DirContextAdapter adapter = new DirContextAdapter(); + adapter.setAttributeValues("objectclass", new String[] { "top", + "person" }); + adapter.setAttributeValue("cn", "Some Person6"); + adapter.setAttributeValue("sn", "Person6"); + adapter.setAttributeValue("description", "Some description"); + + tested.bind(DN, adapter, null); + } + + protected void onTearDown() throws Exception { + tested.unbind(NEWDN); + tested.unbind(DN); + } + + public void testRename() { + tested.rename(DN, NEWDN); + + verifyDeleted(new DistinguishedName(DN)); + verifyBoundCorrectData(); + } + + public void testRename_DistinguishedName() throws Exception { + Name oldDn = new DistinguishedName(DN); + Name newDn = new DistinguishedName(NEWDN); + tested.rename(oldDn, newDn); + + + verifyDeleted(oldDn); + verifyBoundCorrectData(); + } + + private void verifyDeleted(Name dn) { + try { + tested.lookup(dn); + fail("Expected entry '" + dn + "' to be non-existent"); + } catch (NameNotFoundException expected) { + // expected + } + } + + private void verifyBoundCorrectData() { + DirContextAdapter result = (DirContextAdapter) tested.lookup(NEWDN); + assertEquals("Some Person6", result.getStringAttribute("cn")); + assertEquals("Person6", result.getStringAttribute("sn")); + assertEquals("Some description", result + .getStringAttribute("description")); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateSearchCountITest.xls b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateSearchCountITest.xls new file mode 100644 index 0000000000000000000000000000000000000000..a7f7e40c0663e9e9ea892ec76fa2bd8b167de5e8 GIT binary patch literal 11776 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 Dm!0&% literal 0 HcmV?d00001 diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateSearchResultITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateSearchResultITest.java new file mode 100644 index 00000000..f0f3f867 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/LdapTemplateSearchResultITest.java @@ -0,0 +1,181 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap; + +import java.util.List; + +import javax.naming.Name; +import javax.naming.directory.SearchControls; + +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.test.AttributeCheckAttributesMapper; +import org.springframework.ldap.test.AttributeCheckContextMapper; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +/** + * Tests for LdapTemplate's search methods. This test class tests all the + * different versions of the search methods except the generic ones covered in + * other tests. + * + * @author Mattias Arthursson + */ +public class LdapTemplateSearchResultITest extends + AbstractDependencyInjectionSpringContextTests { + + private LdapTemplate tested; + + private AttributeCheckAttributesMapper attributesMapper; + + private AttributeCheckContextMapper contextMapper; + + private static final String[] ALL_ATTRIBUTES = { "cn", "sn", "description", + "telephoneNumber" }; + + private static final String[] CN_SN_ATTRS = { "cn", "sn" }; + + private static final String[] ABSENT_ATTRIBUTES = { "description", + "telephoneNumber" }; + + private static final String[] CN_SN_VALUES = { "Some Person2", "Person2" }; + + private static final String[] ALL_VALUES = { "Some Person2", "Person2", + "Sweden, Company1, Some Person2", "+46 555-654321" }; + + private static final String BASE_STRING = ""; + + private static final String FILTER_STRING = "(&(objectclass=person)(sn=Person2))"; + + private static final Name BASE_NAME = new DistinguishedName(BASE_STRING); + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + protected void onSetUp() throws Exception { + super.onSetUp(); + + attributesMapper = new AttributeCheckAttributesMapper(); + contextMapper = new AttributeCheckContextMapper(); + } + + protected void onTearDown() throws Exception { + super.onTearDown(); + + attributesMapper = null; + contextMapper = null; + } + + public void testSearch_AttributesMapper() { + attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES); + attributesMapper.setExpectedValues(ALL_VALUES); + List list = tested.search(BASE_STRING, FILTER_STRING, attributesMapper); + assertEquals(1, list.size()); + } + + public void testSearch_SearchScope_AttributesMapper() { + attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES); + attributesMapper.setExpectedValues(ALL_VALUES); + List list = tested.search(BASE_STRING, FILTER_STRING, + SearchControls.SUBTREE_SCOPE, attributesMapper); + assertEquals(1, list.size()); + } + + public void testSearch_SearchScope_LimitedAttrs_AttributesMapper() { + attributesMapper.setExpectedAttributes(CN_SN_ATTRS); + attributesMapper.setExpectedValues(CN_SN_VALUES); + attributesMapper.setAbsentAttributes(ABSENT_ATTRIBUTES); + List list = tested.search(BASE_STRING, FILTER_STRING, + SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS, attributesMapper); + assertEquals(1, list.size()); + } + + public void testSearch_AttributesMapper_Name() { + attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES); + attributesMapper.setExpectedValues(ALL_VALUES); + List list = tested.search(BASE_NAME, FILTER_STRING, attributesMapper); + assertEquals(1, list.size()); + } + + public void testSearch_SearchScope_AttributesMapper_Name() { + attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES); + attributesMapper.setExpectedValues(ALL_VALUES); + List list = tested.search(BASE_NAME, FILTER_STRING, + SearchControls.SUBTREE_SCOPE, attributesMapper); + assertEquals(1, list.size()); + } + + public void testSearch_SearchScope_LimitedAttrs_AttributesMapper_Name() { + attributesMapper.setExpectedAttributes(CN_SN_ATTRS); + attributesMapper.setExpectedValues(CN_SN_VALUES); + attributesMapper.setAbsentAttributes(ABSENT_ATTRIBUTES); + List list = tested.search(BASE_NAME, FILTER_STRING, + SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS, attributesMapper); + assertEquals(1, list.size()); + } + + public void testSearch_ContextMapper() { + contextMapper.setExpectedAttributes(ALL_ATTRIBUTES); + contextMapper.setExpectedValues(ALL_VALUES); + List list = tested.search(BASE_STRING, FILTER_STRING, contextMapper); + assertEquals(1, list.size()); + } + + public void testSearch_SearchScope_ContextMapper() { + contextMapper.setExpectedAttributes(ALL_ATTRIBUTES); + contextMapper.setExpectedValues(ALL_VALUES); + List list = tested.search(BASE_STRING, FILTER_STRING, + SearchControls.SUBTREE_SCOPE, contextMapper); + assertEquals(1, list.size()); + } + + public void testSearch_SearchScope_LimitedAttrs_ContextMapper() { + contextMapper.setExpectedAttributes(CN_SN_ATTRS); + contextMapper.setExpectedValues(CN_SN_VALUES); + contextMapper.setAbsentAttributes(ABSENT_ATTRIBUTES); + List list = tested.search(BASE_STRING, FILTER_STRING, + SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS, contextMapper); + assertEquals(1, list.size()); + } + + public void testSearch_ContextMapper_Name() { + contextMapper.setExpectedAttributes(ALL_ATTRIBUTES); + contextMapper.setExpectedValues(ALL_VALUES); + List list = tested.search(BASE_NAME, FILTER_STRING, contextMapper); + assertEquals(1, list.size()); + } + + public void testSearch_SearchScope_ContextMapper_Name() { + contextMapper.setExpectedAttributes(ALL_ATTRIBUTES); + contextMapper.setExpectedValues(ALL_VALUES); + List list = tested.search(BASE_NAME, FILTER_STRING, + SearchControls.SUBTREE_SCOPE, contextMapper); + assertEquals(1, list.size()); + } + + public void testSearch_SearchScope_LimitedAttrs_ContextMapper_Name() { + contextMapper.setExpectedAttributes(CN_SN_ATTRS); + contextMapper.setExpectedValues(CN_SN_VALUES); + contextMapper.setAbsentAttributes(ABSENT_ATTRIBUTES); + List list = tested.search(BASE_NAME, FILTER_STRING, + SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS, contextMapper); + assertEquals(1, list.size()); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/authentication/AcegiAuthenticationSourceITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/authentication/AcegiAuthenticationSourceITest.java new file mode 100644 index 00000000..8e562008 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/authentication/AcegiAuthenticationSourceITest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.authentication; + +import java.util.List; + +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + + +import org.acegisecurity.Authentication; +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.acegisecurity.providers.ldap.LdapAuthenticationProvider; +import org.springframework.ldap.authentication.AcegiAuthenticationSource; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +public class AcegiAuthenticationSourceITest extends + AbstractDependencyInjectionSpringContextTests { + + private LdapAuthenticationProvider ldapAuthProvider; + + private LdapTemplate ldapTemplate; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateAcegiTestContext.xml" }; + } + + public void setLdapAuthProvider(LdapAuthenticationProvider ldapAuthProvider) { + this.ldapAuthProvider = ldapAuthProvider; + } + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public void testGetPrincipalAndCredentials() { + Authentication authentication = ldapAuthProvider + .authenticate(new UsernamePasswordAuthenticationToken( + "Some Person3", "password")); + SecurityContextHolder.getContext().setAuthentication(authentication); + + AcegiAuthenticationSource tested = new AcegiAuthenticationSource(); + assertEquals("cn=Some Person3,ou=company1,c=Sweden,dc=jayway,dc=se", + tested.getPrincipal()); + assertEquals("password", tested.getCredentials()); + } + + public void testSearchIndividualAuthentication() { + Authentication authentication = ldapAuthProvider + .authenticate(new UsernamePasswordAuthenticationToken( + "Some Person3", "password")); + SecurityContextHolder.getContext().setAuthentication(authentication); + + List result = ldapTemplate.search("dc=jayway,dc=se", + "(objectclass=person)", new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + return attributes.get("cn"); + } + }); + assertEquals(5, result.size()); + } + +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/control/SupportedControlsITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/control/SupportedControlsITest.java new file mode 100644 index 00000000..58880164 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/control/SupportedControlsITest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.control; + +import java.util.Arrays; + +import org.springframework.ldap.AbstractLdapTemplateIntegrationTest; +import org.springframework.ldap.core.ContextMapper; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.LdapTemplate; + +/** + * Provides tests that verify that the server supports certain controls. + * + * @author Ulrik Sandberg + */ +public class SupportedControlsITest extends AbstractLdapTemplateIntegrationTest { + /** must use a context source that has no base set */ + private LdapTemplate tested; + + private static final String SUPPORTED_CONTROL = "supportedcontrol"; + + protected String[] getConfigLocations() { + return new String[] { "/conf/rootContextSourceTestContext.xml" }; + } + + public void testExpectedControlsSupported() throws Exception { + /** + * Maps the 'supportedcontrol' attribute to a string array. + */ + ContextMapper mapper = new ContextMapper() { + + public Object mapFromContext(Object ctx) { + DirContextAdapter adapter = (DirContextAdapter) ctx; + return adapter.getStringAttributes(SUPPORTED_CONTROL); + } + + }; + + String[] controls = (String[]) tested.lookup("", + new String[] { SUPPORTED_CONTROL }, mapper); + System.out.println(Arrays.toString(controls)); + assertEquals("Persistent Search LDAPv3 control,", + "2.16.840.1.113730.3.4.3", controls[0]); + assertEquals("Entry Change Notification LDAPv3 control,", + "2.16.840.1.113730.3.4.7", controls[1]); + assertEquals("Subentries Control,", "1.3.6.1.4.1.4203.1.10.1", + controls[2]); + assertEquals("Manage DSA IT LDAPv3 control,", + "2.16.840.1.113730.3.4.2", controls[3]); + } + + public void setTested(LdapTemplate tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DistinguishedNameEditorITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DistinguishedNameEditorITest.java new file mode 100644 index 00000000..4450e8f8 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DistinguishedNameEditorITest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +/** + * Integration tests for {@link DistinguishedNameEditor}. + * + * @author Mattias Arthursson + */ +public class DistinguishedNameEditorITest extends AbstractDependencyInjectionSpringContextTests { + + private DummyDistinguishedNameConsumer distinguishedNameConsumer; + + protected String[] getConfigLocations() { + return new String[] { "classpath:conf/distinguishedNameEditorTestContext.xml" }; + } + + public void testDistinguishedNameEditor() throws Exception { + assertNotNull(distinguishedNameConsumer); + DistinguishedName name = distinguishedNameConsumer.getDistinguishedName(); + assertEquals(new DistinguishedName("dc=jayway, dc=se"), name); + } + + public void setDistinguishedNameConsumer(DummyDistinguishedNameConsumer distinguishedNameConsumer) { + this.distinguishedNameConsumer = distinguishedNameConsumer; + } + +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DnParsePerformanceITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DnParsePerformanceITest.java new file mode 100644 index 00000000..6a31ff76 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DnParsePerformanceITest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import org.springframework.ldap.core.DistinguishedName; + +import junit.framework.Test; +import junit.framework.TestCase; + +import com.clarkware.junitperf.LoadTest; + +/** + * Performance test for the {@link DistinguishedName} class. + * + * @author Ulrik Sandberg + */ +public class DnParsePerformanceITest extends TestCase { + + public DnParsePerformanceITest(String name) { + super(name); + } + + /** + * Tests parsing and toString. + */ + public void testContains() { + + DistinguishedName migpath = new DistinguishedName("OU=G,OU=I,OU=M"); + DistinguishedName path1 = new DistinguishedName( + "cn=john.doe, OU=Users,OU=SE,OU=G,OU=I,OU=M"); + DistinguishedName path2 = new DistinguishedName( + "cn=john.doe, OU=Users,OU=SE,ou=G,OU=i,OU=M, ou=foo"); + DistinguishedName path3 = new DistinguishedName( + "ou=G,OU=i,OU=M, ou=foo"); + DistinguishedName path4 = new DistinguishedName("ou=G,OU=i,ou=m"); + + DistinguishedName pathE1 = new DistinguishedName( + "cn=john.doe, OU=Users,OU=SE,ou=G,OU=L,OU=M, ou=foo"); + DistinguishedName pathE2 = new DistinguishedName( + "cn=john.doe, OU=Users,OU=SE"); + } + + public static Test suite() { + int users = 2000; + Test testCase = new DnParsePerformanceITest("testContains"); + Test loadTest = new LoadTest(testCase, users); + return loadTest; + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } +} \ No newline at end of file diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DummyDistinguishedNameConsumer.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DummyDistinguishedNameConsumer.java new file mode 100644 index 00000000..0b9c800d --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/DummyDistinguishedNameConsumer.java @@ -0,0 +1,34 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core; + +/** + * Dummy implementation of a class that has a {@link DistinguishedName} setter, + * used for testing purposes. + * + * @author Mattias Arthursson + */ +public class DummyDistinguishedNameConsumer { + private DistinguishedName distinguishedName; + + public DistinguishedName getDistinguishedName() { + return distinguishedName; + } + + public void setDistinguishedName(DistinguishedName distinguishedName) { + this.distinguishedName = distinguishedName; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostprocessorITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostprocessorITest.java new file mode 100644 index 00000000..ea001ef6 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/BaseLdapPathBeanPostprocessorITest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import junit.framework.TestCase; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.ldap.core.DistinguishedName; + +/** + * Integration tests for {@link BaseLdapPathBeanPostProcessor}. + * + * @author Mattias Arthursson + */ +public class BaseLdapPathBeanPostprocessorITest extends TestCase { + + protected String[] getConfigLocations() { + return new String[] { "/conf/baseLdapPathPostProcessorTestContext.xml" }; + } + + public void testPostProcessBeforeInitialization() throws Exception { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( + "/conf/baseLdapPathPostProcessorTestContext.xml"); + DummyBaseLdapPathAware tested = (DummyBaseLdapPathAware) ctx.getBean("dummyBaseContextAware"); + + DistinguishedName base = tested.getBase(); + assertNotNull(base); + assertEquals(new DistinguishedName("dc=jayway,dc=se"), base); + } + + public void testPostProcessBeforeInitializationMultipleContextSources() throws Exception { + try { + new ClassPathXmlApplicationContext("/conf/baseLdapPathPostProcessorMultiContextSourceTestContext.xml"); + fail("BeanCreationException expected"); + } + catch (BeanCreationException expected) { + Throwable cause = expected.getCause(); + assertTrue(cause instanceof NoSuchBeanDefinitionException); + } + } + + public void testPostProcessBeforeInitializationMultipleContextSourcesOneSpecified() throws Exception { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( + "/conf/baseLdapPathPostProcessorMultiContextSourceOneSpecTestContext.xml"); + DummyBaseLdapPathAware tested = (DummyBaseLdapPathAware) ctx.getBean("dummyBaseContextAware"); + + DistinguishedName base = tested.getBase(); + assertNotNull(base); + assertEquals(new DistinguishedName("cn=john doe,dc=jayway,dc=se"), base); + } + + public void testPostProcessBeforeInitializationNoContextSource() throws Exception { + try { + new ClassPathXmlApplicationContext("/conf/baseLdapPathPostProcessorNoContextSourceTestContext.xml"); + fail("BeanCreationException expected"); + } + catch (BeanCreationException expected) { + Throwable cause = expected.getCause(); + assertTrue(cause instanceof NoSuchBeanDefinitionException); + } + } + + public void testPostProcessBeforeInitializationBaseSetInProperty() throws Exception { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( + "/conf/baseLdapPathPostProcessorPropertyOverrideTestContext.xml"); + DummyBaseLdapPathAware tested = (DummyBaseLdapPathAware) ctx.getBean("dummyBaseContextAware"); + + DistinguishedName base = tested.getBase(); + assertNotNull(base); + assertEquals(new DistinguishedName("cn=john doe"), base); + } + + public void testPostProcessBeforeInitializationTransactionProxy() throws Exception { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( + "/conf/baseLdapPathPostProcessorTransactionTestContext.xml"); + DummyBaseLdapPathAware tested = (DummyBaseLdapPathAware) ctx.getBean("dummyBaseContextAware"); + + DistinguishedName base = tested.getBase(); + assertNotNull(base); + assertEquals(new DistinguishedName("dc=jayway,dc=se"), base); + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/DummyBaseLdapPathAware.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/DummyBaseLdapPathAware.java new file mode 100644 index 00000000..443980b3 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/DummyBaseLdapPathAware.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import org.springframework.ldap.core.DistinguishedName; + +/** + * Dummy implementation of {@link BaseLdapPathAware}. + * + * @author Mattias Arthursson + */ +public class DummyBaseLdapPathAware implements BaseLdapPathAware { + + private DistinguishedName base; + + public void setBaseLdapPath(DistinguishedName baseLdapPath) { + this.base = baseLdapPath; + } + + public DistinguishedName getBase() { + return base; + } + +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/LdapContextSourcelITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/LdapContextSourcelITest.java new file mode 100644 index 00000000..d1f5df7c --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/LdapContextSourcelITest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import org.springframework.ldap.core.ContextSource; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +/** + * Integration tests for ContextSourceImpl. + * + * @author Mattias Arthursson + */ +public class LdapContextSourcelITest extends + AbstractDependencyInjectionSpringContextTests { + + private ContextSource tested; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + public void testGetReadOnlyContext() throws NamingException { + DirContext ctx = null; + + try { + ctx = tested.getReadOnlyContext(); + assertNotNull(ctx); + Hashtable environment = ctx.getEnvironment(); + assertTrue(environment + .containsKey(LdapContextSource.SUN_LDAP_POOLING_FLAG)); + assertTrue(environment.containsKey(Context.SECURITY_PRINCIPAL)); + assertTrue(environment.containsKey(Context.SECURITY_CREDENTIALS)); + } finally { + // Always clean up. + if (ctx != null) { + try { + ctx.close(); + } catch (Exception e) { + // Never mind this + } + } + } + } + + public void testGetReadWriteContext() throws NamingException { + DirContext ctx = null; + + try { + ctx = tested.getReadWriteContext(); + assertNotNull(ctx); + // Double check to see that we are authenticated. + Hashtable environment = ctx.getEnvironment(); + assertTrue(environment.containsKey(Context.SECURITY_PRINCIPAL)); + assertTrue(environment.containsKey(Context.SECURITY_CREDENTIALS)); + } finally { + // Always clean up. + if (ctx != null) { + try { + ctx.close(); + } catch (Exception e) { + // Never mind this + } + } + } + } + + public void setTested(ContextSource tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/LdapContextSourcelMultiServerITest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/LdapContextSourcelMultiServerITest.java new file mode 100644 index 00000000..caec3a68 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/core/support/LdapContextSourcelMultiServerITest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.support; + +import javax.naming.NamingException; + +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +/** + * Integration tests for ContextSourceImpl. + * + * @author Mattias Arthursson + */ +public class LdapContextSourcelMultiServerITest extends + AbstractDependencyInjectionSpringContextTests { + + private LdapContextSource tested; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapContextSourceTestContext.xml" }; + } + + public void testUrls() throws NamingException { + String[] urls = tested.getUrls(); + String string = tested.assembleProviderUrlString(urls); + + assertEquals("ldap://127.0.0.1:389 ldap://127.0.0.2:389", string); + } + + public void setTested(LdapContextSource tested) { + this.tested = tested; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManagerIntegrationTest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManagerIntegrationTest.java new file mode 100644 index 00000000..ee844529 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceAndDataSourceTransactionManagerIntegrationTest.java @@ -0,0 +1,371 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.ldap.NameNotFoundException; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Integration tests for {@link ContextSourceAndDataSourceTransactionManager}. + * + * @author Mattias Arthursson + */ +public class ContextSourceAndDataSourceTransactionManagerIntegrationTest extends + AbstractDependencyInjectionSpringContextTests { + + private static Log log = LogFactory + .getLog(ContextSourceAndDataSourceTransactionManagerIntegrationTest.class); + + private DummyDao dummyDao; + + private LdapTemplate ldapTemplate; + + private JdbcTemplate jdbcTemplate; + + public ContextSourceAndDataSourceTransactionManagerIntegrationTest() { + setAutowireMode(AbstractDependencyInjectionSpringContextTests.AUTOWIRE_BY_NAME); + } + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public void setDummyDao(DummyDao dummyDaoImpl) { + this.dummyDao = dummyDaoImpl; + } + + protected String[] getConfigLocations() { + return new String[] { "conf/ldapAndJdbcTransactionTestContext.xml" }; + } + + protected void onSetUp() throws Exception { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clearSynchronization(); + } + + jdbcTemplate + .execute("create table PERSON(fullname VARCHAR, lastname VARCHAR, description VARCHAR)"); + jdbcTemplate.update("insert into PERSON values(?, ?, ?)", new Object[] { + "Some Person", "Person", "Sweden, Company1, Some Person" }); + } + + protected void onTearDown() throws Exception { + jdbcTemplate.execute("drop table PERSON"); + } + + public void testCreateWithException() { + try { + dummyDao.createWithException("Sweden", "company1", + "some testperson", "testperson", "some description"); + fail("DummyException expected"); + } catch (DummyException expected) { + assertTrue(true); + } + + log.debug("Verifying result"); + + // Verify that no entry was created + try { + ldapTemplate.lookup("cn=some testperson, ou=company1, c=Sweden"); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + try { + jdbcTemplate.queryForObject( + "select * from PERSON where fullname='some testperson'", + new RowMapper() { + public Object mapRow(ResultSet rs, int rowNum) + throws SQLException { + return null; + } + }); + fail("EmptyResultDataAccessException expected"); + } catch (EmptyResultDataAccessException expected) { + assertTrue(true); + } + } + + public void testCreate() { + dummyDao.create("Sweden", "company1", "some testperson", "testperson", + "some description"); + + log.debug("Verifying result"); + Object ldapResult = ldapTemplate + .lookup("cn=some testperson, ou=company1, c=Sweden"); + Object dbResult = jdbcTemplate.queryForObject( + "select * from PERSON where fullname='some testperson'", + new RowMapper() { + public Object mapRow(ResultSet rs, int rowNum) + throws SQLException { + return new Object(); + } + }); + assertNotNull(ldapResult); + assertNotNull(dbResult); + + ldapTemplate.unbind("cn=some testperson, ou=company1, c=Sweden"); + } + + public void testUpdateWithException() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + try { + dummyDao.updateWithException(dn, "Some Person", "Updated Person", + "Updated description"); + fail("DummyException expected"); + } catch (DummyException expected) { + assertTrue(true); + } + + log.debug("Verifying result"); + + Object ldapResult = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Person", attributes.get("sn").get()); + assertEquals("Sweden, Company1, Some Person", attributes.get( + "description").get()); + return new Object(); + } + }); + + Object jdbcResult = jdbcTemplate.queryForObject( + "select * from PERSON where fullname=?", + new Object[] { "Some Person" }, new RowMapper() { + public Object mapRow(ResultSet rs, int rowNum) + throws SQLException { + assertEquals("Person", rs.getString("lastname")); + assertEquals("Sweden, Company1, Some Person", rs + .getString("description")); + return new Object(); + } + }); + + assertNotNull(ldapResult); + assertNotNull(jdbcResult); + } + + public void testUpdate() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + dummyDao.update(dn, "Some Person", "Updated Person", + "Updated description"); + + log.debug("Verifying result"); + Object ldapResult = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Updated Person", attributes.get("sn").get()); + assertEquals("Updated description", attributes.get( + "description").get()); + return new Object(); + } + }); + + Object jdbcResult = jdbcTemplate.queryForObject( + "select * from PERSON where fullname=?", + new Object[] { "Some Person" }, new RowMapper() { + public Object mapRow(ResultSet rs, int rowNum) + throws SQLException { + assertEquals("Updated Person", rs.getString("lastname")); + assertEquals("Updated description", rs + .getString("description")); + return new Object(); + } + }); + + assertNotNull(ldapResult); + assertNotNull(jdbcResult); + dummyDao.update(dn, "Some Person", "Person", + "Sweden, Company1, Some Person"); + } + + public void testUpdateAndRenameWithException() { + String dn = "cn=Some Person2,ou=company1,c=Sweden"; + String newDn = "cn=Some Person2,ou=company2,c=Sweden"; + try { + // Perform test + dummyDao.updateAndRenameWithException(dn, newDn, + "Updated description"); + fail("DummyException expected"); + } catch (DummyException expected) { + assertTrue(true); + } + + // Verify that entry was not moved. + try { + ldapTemplate.lookup(newDn); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + // Verify that original entry was not updated. + Object object = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Sweden, Company1, Some Person2", attributes.get( + "description").get()); + return new Object(); + } + }); + assertNotNull(object); + } + + public void testUpdateAndRename() { + String dn = "cn=Some Person2,ou=company1,c=Sweden"; + String newDn = "cn=Some Person2,ou=company2,c=Sweden"; + // Perform test + dummyDao.updateAndRename(dn, newDn, "Updated description"); + + // Verify that entry was moved and updated. + Object object = ldapTemplate.lookup(newDn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Updated description", attributes.get( + "description").get()); + return new Object(); + } + }); + + assertNotNull(object); + dummyDao.updateAndRename(newDn, dn, "Sweden, Company1, Some Person2"); + } + + public void testModifyAttributesWithException() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + try { + // Perform test + dummyDao.modifyAttributesWithException(dn, "Updated lastname", + "Updated description"); + fail("DummyException expected"); + } catch (DummyException expected) { + assertTrue(true); + } + + // Verify result - check that the operation was properly rolled back + Object result = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Person", attributes.get("sn").get()); + assertEquals("Sweden, Company1, Some Person", attributes.get( + "description").get()); + return new Object(); + } + }); + + assertNotNull(result); + } + + public void testModifyAttributes() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + // Perform test + dummyDao + .modifyAttributes(dn, "Updated lastname", "Updated description"); + + // Verify result - check that the operation was not rolled back + Object result = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Updated lastname", attributes.get("sn").get()); + assertEquals("Updated description", attributes.get( + "description").get()); + return new Object(); + } + }); + + assertNotNull(result); + } + + public void testUnbindWithException() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + try { + // Perform test + dummyDao.unbindWithException(dn, "Some Person"); + fail("DummyException expected"); + } catch (DummyException expected) { + assertTrue(true); + } + + // Verify result - check that the operation was properly rolled back + Object ldapResult = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + // Just verify that the entry still exists. + return new Object(); + } + }); + + Object jdbcResult = jdbcTemplate.queryForObject( + "select * from PERSON where fullname=?", + new Object[] { "Some Person" }, new RowMapper() { + public Object mapRow(ResultSet rs, int rowNum) + throws SQLException { + // Just verify that the entry still exists. + return new Object(); + } + }); + + assertNotNull(ldapResult); + assertNotNull(jdbcResult); + } + + public void testUnbind() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + // Perform test + dummyDao.unbind(dn, "Some Person"); + + try { + // Verify result - check that the operation was not rolled back + ldapTemplate.lookup(dn); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + try { + jdbcTemplate.queryForObject( + "select * from PERSON where fullname=?", + new Object[] { "Some Person" }, new RowMapper() { + public Object mapRow(ResultSet rs, int rowNum) + throws SQLException { + return null; + } + }); + fail("EmptyResultDataAccessException expected"); + } catch (EmptyResultDataAccessException expected) { + assertTrue(true); + } + } + + public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerIntegrationTest.java b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerIntegrationTest.java new file mode 100644 index 00000000..aaf94e12 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/org/springframework/ldap/transaction/compensating/manager/ContextSourceTransactionManagerIntegrationTest.java @@ -0,0 +1,281 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.transaction.compensating.manager; + +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ldap.NameNotFoundException; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Integration tests for {@link ContextSourceAndDataSourceTransactionManager}. + * + * @author Mattias Arthursson + */ +public class ContextSourceTransactionManagerIntegrationTest extends + AbstractDependencyInjectionSpringContextTests { + + private static Log log = LogFactory + .getLog(ContextSourceTransactionManagerIntegrationTest.class); + + public ContextSourceTransactionManagerIntegrationTest() { + setAutowireMode(AbstractDependencyInjectionSpringContextTests.AUTOWIRE_BY_NAME); + } + + private DummyDao dummyDao; + + private LdapTemplate ldapTemplate; + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public void setDummyDao(DummyDao dummyDaoImpl) { + this.dummyDao = dummyDaoImpl; + } + + protected String[] getConfigLocations() { + return new String[] { "conf/ldapTemplateTransactionTestContext.xml" }; + } + + protected void onSetUp() throws Exception { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clearSynchronization(); + } + } + + protected void onTearDown() throws Exception { + } + + public void testCreateWithException() { + try { + dummyDao.createWithException("Sweden", "company1", + "some testperson", "testperson", "some description"); + fail("DummyException expected"); + } catch (DummyException expected) { + assertTrue(true); + } + + log.debug("Verifying result"); + + // Verify that no entry was created + try { + ldapTemplate.lookup("cn=some testperson, ou=company1, c=Sweden"); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + } + + public void testCreate() { + dummyDao.create("Sweden", "company1", "some testperson", "testperson", + "some description"); + + log.debug("Verifying result"); + String expectedDn = "cn=some testperson, ou=company1, c=Sweden"; + Object ldapResult = ldapTemplate.lookup(expectedDn); + assertNotNull(ldapResult); + + ldapTemplate.unbind(expectedDn); + } + + public void testUpdateWithException() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + try { + dummyDao.updateWithException(dn, "Some Person", "Updated Person", + "Updated description"); + fail("DummyException expected"); + } catch (DummyException expected) { + assertTrue(true); + } + + log.debug("Verifying result"); + + Object ldapResult = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Person", attributes.get("sn").get()); + assertEquals("Sweden, Company1, Some Person", attributes.get( + "description").get()); + return new Object(); + } + }); + + assertNotNull(ldapResult); + } + + public void testUpdate() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + dummyDao.update(dn, "Some Person", "Updated Person", + "Updated description"); + + log.debug("Verifying result"); + Object ldapResult = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Updated Person", attributes.get("sn").get()); + assertEquals("Updated description", attributes.get( + "description").get()); + return new Object(); + } + }); + + assertNotNull(ldapResult); + + dummyDao.update(dn, "Some Person", "Person", + "Sweden, Company1, Some Person"); + } + + public void testUpdateAndRenameWithException() { + String dn = "cn=Some Person2,ou=company1,c=Sweden"; + String newDn = "cn=Some Person2,ou=company2,c=Sweden"; + try { + // Perform test + dummyDao.updateAndRenameWithException(dn, newDn, + "Updated description"); + fail("DummyException expected"); + } catch (DummyException expected) { + assertTrue(true); + } + + // Verify that entry was not moved. + try { + ldapTemplate.lookup(newDn); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + // Verify that original entry was not updated. + Object object = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Sweden, Company1, Some Person2", attributes.get( + "description").get()); + return new Object(); + } + }); + assertNotNull(object); + } + + public void testUpdateAndRename() { + String dn = "cn=Some Person2,ou=company1,c=Sweden"; + String newDn = "cn=Some Person2,ou=company2,c=Sweden"; + // Perform test + dummyDao.updateAndRename(dn, newDn, "Updated description"); + + // Verify that entry was moved and updated. + Object object = ldapTemplate.lookup(newDn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Updated description", attributes.get( + "description").get()); + return new Object(); + } + }); + + assertNotNull(object); + dummyDao.updateAndRename(newDn, dn, "Sweden, Company1, Some Person2"); + } + + public void testModifyAttributesWithException() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + try { + // Perform test + dummyDao.modifyAttributesWithException(dn, "Updated lastname", + "Updated description"); + fail("DummyException expected"); + } catch (DummyException expected) { + assertTrue(true); + } + + // Verify result - check that the operation was properly rolled back + Object result = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Person", attributes.get("sn").get()); + assertEquals("Sweden, Company1, Some Person", attributes.get( + "description").get()); + return new Object(); + } + }); + + assertNotNull(result); + } + + public void testModifyAttributes() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + // Perform test + dummyDao + .modifyAttributes(dn, "Updated lastname", "Updated description"); + + // Verify result - check that the operation was not rolled back + Object result = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + assertEquals("Updated lastname", attributes.get("sn").get()); + assertEquals("Updated description", attributes.get( + "description").get()); + return new Object(); + } + }); + + assertNotNull(result); + } + + public void testUnbindWithException() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + try { + // Perform test + dummyDao.unbindWithException(dn, "Some Person"); + fail("DummyException expected"); + } catch (DummyException expected) { + assertTrue(true); + } + + // Verify result - check that the operation was properly rolled back + Object ldapResult = ldapTemplate.lookup(dn, new AttributesMapper() { + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + // Just verify that the entry still exists. + return new Object(); + } + }); + + assertNotNull(ldapResult); + } + + public void testUnbind() { + String dn = "cn=Some Person,ou=company1,c=Sweden"; + // Perform test + dummyDao.unbind(dn, "Some Person"); + + try { + // Verify result - check that the operation was not rolled back + ldapTemplate.lookup(dn); + fail("NameNotFoundException expected"); + } catch (NameNotFoundException expected) { + assertTrue(true); + } + + } +} diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/setup_data.ldif b/mvn-build/integration-tests/core-integration-tests/src/test/java/setup_data.ldif new file mode 100644 index 00000000..885a8ca4 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/setup_data.ldif @@ -0,0 +1,110 @@ +dn: ou=groups,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: groups + +dn: cn=ROLE_USER,ou=groups,dc=jayway,dc=se +objectclass: top +objectclass: groupOfUniqueNames +cn: ROLE_USER +uniqueMember: cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se +uniqueMember: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se +uniqueMember: cn=Some Person,ou=company1,c=Norway,dc=jayway,dc=se +uniqueMember: cn=Some Person,ou=company2,c=Sweden,dc=jayway,dc=se +uniqueMember: cn=Some Person3,ou=company1,c=Sweden,dc=jayway,dc=se + +dn: cn=ROLE_ADMIN,ou=groups,dc=jayway,dc=se +objectclass: top +objectclass: groupOfUniqueNames +cn: ROLE_ADMIN +uniqueMember: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se + +dn: c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: country +c: Sweden +description: The country of Sweden + +dn: c=Norway,dc=jayway,dc=se +objectclass: top +objectclass: country +c: Norway +description: The country of Norway + +dn: ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: company1 +description: First company in Sweden + +dn: ou=company2,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: company2 +description: Second company in Sweden + +dn: ou=company1,c=Norway,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: company1 +description: First company in Norway + +dn: cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person +userPassword: password +cn: Some Person +sn: Person +description: Sweden, Company1, Some Person +telephoneNumber: +46 555-123456 + +dn: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person2 +userPassword: password +cn: Some Person2 +sn: Person2 +description: Sweden, Company1, Some Person2 +telephoneNumber: +46 555-654321 + +dn: cn=Some Person3,ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person3 +userPassword: password +cn: Some Person3 +sn: Person3 +description: Sweden, Company1, Some Person3 +telephoneNumber: +46 555-123654 + +dn: cn=Some Person,ou=company2,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person4 +userPassword: password +cn: Some Person +sn: Person +description: Sweden, Company2, Some Person +telephoneNumber: +46 555-456321 + +dn: cn=Some Person+sn=Person,ou=company1,c=Norway,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.norwegian +userPassword: password +cn: Some Person +sn: Person +description: Norway, Company1, Some Person+Person +telephoneNumber: +45 555-654123 diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/java/teardown_data.ldif b/mvn-build/integration-tests/core-integration-tests/src/test/java/teardown_data.ldif new file mode 100644 index 00000000..f6ada1b2 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/java/teardown_data.ldif @@ -0,0 +1,5 @@ +ou=groups,dc=jayway,dc=se + +c=Sweden,dc=jayway,dc=se + +c=Norway,dc=jayway,dc=se diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/apacheDsContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/apacheDsContext.xml new file mode 100644 index 00000000..6620b80d --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/apacheDsContext.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + simple + + + ${userDn} + + + ${password} + + + + + + + + + + + + + + + + + + + + + objectClass: top + objectClass: domain + objectClass: extensibleObject + dc: jayway + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorMultiContextSourceOneSpecTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorMultiContextSourceOneSpecTestContext.xml new file mode 100644 index 00000000..2d312a68 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorMultiContextSourceOneSpecTestContext.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorMultiContextSourceTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorMultiContextSourceTestContext.xml new file mode 100644 index 00000000..1a1d104c --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorMultiContextSourceTestContext.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorNoContextSourceTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorNoContextSourceTestContext.xml new file mode 100644 index 00000000..52798e86 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorNoContextSourceTestContext.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorPropertyOverrideTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorPropertyOverrideTestContext.xml new file mode 100644 index 00000000..8b42a613 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorPropertyOverrideTestContext.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorTestContext.xml new file mode 100644 index 00000000..49a67621 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorTestContext.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorTransactionTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorTransactionTestContext.xml new file mode 100644 index 00000000..066e6a58 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/baseLdapPathPostProcessorTransactionTestContext.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/commonTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/commonTestContext.xml new file mode 100644 index 00000000..a71c074e --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/commonTestContext.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/distinguishedNameEditorTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/distinguishedNameEditorTestContext.xml new file mode 100644 index 00000000..86f40c59 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/distinguishedNameEditorTestContext.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldap.properties b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldap.properties new file mode 100644 index 00000000..8843f1f1 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldap.properties @@ -0,0 +1,4 @@ +urls=ldap://127.0.0.1:3900 +userDn=uid=admin,ou=system +password=secret +base=dc=jayway,dc=se diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapAndJdbcTransactionTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapAndJdbcTransactionTestContext.xml new file mode 100644 index 00000000..abaaab86 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapAndJdbcTransactionTestContext.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PROPAGATION_REQUIRES_NEW + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapContextSourceTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapContextSourceTestContext.xml new file mode 100644 index 00000000..2adc5219 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapContextSourceTestContext.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateAcegiTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateAcegiTestContext.xml new file mode 100644 index 00000000..a7ba9c2c --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateAcegiTestContext.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateNoBaseSuffixTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateNoBaseSuffixTestContext.xml new file mode 100644 index 00000000..312b761f --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateNoBaseSuffixTestContext.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateObjectBindTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateObjectBindTestContext.xml new file mode 100644 index 00000000..f67e95d5 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateObjectBindTestContext.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateTestContext.xml new file mode 100644 index 00000000..3c85161c --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateTestContext.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateTransactionTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateTransactionTestContext.xml new file mode 100644 index 00000000..8b111f02 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/ldapTemplateTransactionTestContext.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PROPAGATION_REQUIRES_NEW + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/rootContextSourceTestContext.xml b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/rootContextSourceTestContext.xml new file mode 100644 index 00000000..13cc7894 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/conf/rootContextSourceTestContext.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/log4j.properties b/mvn-build/integration-tests/core-integration-tests/src/test/resources/log4j.properties new file mode 100644 index 00000000..12c4b1c8 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootCategory=warn, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout.ConversionPattern=%r [%t] %-5p\: %-15c{2} - %m%n +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +log4j.logger.org.apache.directory.server.schema.registries.DefaultSyntaxRegistry=WARN \ No newline at end of file diff --git a/mvn-build/integration-tests/core-integration-tests/src/test/resources/setup_data.ldif b/mvn-build/integration-tests/core-integration-tests/src/test/resources/setup_data.ldif new file mode 100644 index 00000000..885a8ca4 --- /dev/null +++ b/mvn-build/integration-tests/core-integration-tests/src/test/resources/setup_data.ldif @@ -0,0 +1,110 @@ +dn: ou=groups,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: groups + +dn: cn=ROLE_USER,ou=groups,dc=jayway,dc=se +objectclass: top +objectclass: groupOfUniqueNames +cn: ROLE_USER +uniqueMember: cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se +uniqueMember: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se +uniqueMember: cn=Some Person,ou=company1,c=Norway,dc=jayway,dc=se +uniqueMember: cn=Some Person,ou=company2,c=Sweden,dc=jayway,dc=se +uniqueMember: cn=Some Person3,ou=company1,c=Sweden,dc=jayway,dc=se + +dn: cn=ROLE_ADMIN,ou=groups,dc=jayway,dc=se +objectclass: top +objectclass: groupOfUniqueNames +cn: ROLE_ADMIN +uniqueMember: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se + +dn: c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: country +c: Sweden +description: The country of Sweden + +dn: c=Norway,dc=jayway,dc=se +objectclass: top +objectclass: country +c: Norway +description: The country of Norway + +dn: ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: company1 +description: First company in Sweden + +dn: ou=company2,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: company2 +description: Second company in Sweden + +dn: ou=company1,c=Norway,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: company1 +description: First company in Norway + +dn: cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person +userPassword: password +cn: Some Person +sn: Person +description: Sweden, Company1, Some Person +telephoneNumber: +46 555-123456 + +dn: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person2 +userPassword: password +cn: Some Person2 +sn: Person2 +description: Sweden, Company1, Some Person2 +telephoneNumber: +46 555-654321 + +dn: cn=Some Person3,ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person3 +userPassword: password +cn: Some Person3 +sn: Person3 +description: Sweden, Company1, Some Person3 +telephoneNumber: +46 555-123654 + +dn: cn=Some Person,ou=company2,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person4 +userPassword: password +cn: Some Person +sn: Person +description: Sweden, Company2, Some Person +telephoneNumber: +46 555-456321 + +dn: cn=Some Person+sn=Person,ou=company1,c=Norway,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.norwegian +userPassword: password +cn: Some Person +sn: Person +description: Norway, Company1, Some Person+Person +telephoneNumber: +45 555-654123 diff --git a/mvn-build/integration-tests/pom.xml b/mvn-build/integration-tests/pom.xml new file mode 100644 index 00000000..a3149a37 --- /dev/null +++ b/mvn-build/integration-tests/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + org.springframework.ldap + spring-ldap-integration-tests + 1.2.2-SNAPSHOT + + pom + Spring LDAP Integration Tests + + + core-integration-tests + tiger-integration-tests + + diff --git a/mvn-build/integration-tests/tiger-integration-tests/pom.xml b/mvn-build/integration-tests/tiger-integration-tests/pom.xml new file mode 100644 index 00000000..eb9fb41c --- /dev/null +++ b/mvn-build/integration-tests/tiger-integration-tests/pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + org.springframework.ldap + spring-ldap-tiger-integration-tests + Spring LDAP Tiger Integration Tests + 1.2.2-SNAPSHOT + + + 2.0.8 + + + + + + maven-eclipse-plugin + + true + + + + + maven-compiler-plugin + + 1.5 + 1.5 + + + + + + + + org.springframework.ldap + spring-ldap-test + 1.2.2-SNAPSHOT + + + org.springframework.ldap + spring-ldap-tiger + 1.2.2-SNAPSHOT + + + commons-pool + commons-pool + 1.3 + provided + + + com.sun + ldapbp + 1.0 + system + ${basedir}/../../lib/ldapbp.jar + + + + org.springframework + spring-dao + ${spring.version} + provided + + + org.springframework + spring-jdbc + ${spring.version} + provided + + + org.springframework + spring-aop + ${spring.version} + test + + + aspectj + aspectjrt + 1.5.3 + test + + + aspectj + aspectjweaver + 1.5.3 + test + + + gsbase + gsbase + 2.0.1 + test + + + hsqldb + hsqldb + 1.8.0.7 + test + + + diff --git a/mvn-build/integration-tests/tiger-integration-tests/src/test/java/org/springframework/ldap/core/simple/SimpleLdapTemplateITest.java b/mvn-build/integration-tests/tiger-integration-tests/src/test/java/org/springframework/ldap/core/simple/SimpleLdapTemplateITest.java new file mode 100644 index 00000000..6da647cd --- /dev/null +++ b/mvn-build/integration-tests/tiger-integration-tests/src/test/java/org/springframework/ldap/core/simple/SimpleLdapTemplateITest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.simple; + +import java.util.List; + +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DirContextOperations; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +public class SimpleLdapTemplateITest extends AbstractDependencyInjectionSpringContextTests { + + private SimpleLdapTemplate ldapTemplate; + + protected String[] getConfigLocations() { + return new String[] { "/conf/ldapTemplateTestContext.xml" }; + } + + public void setSimplLdapTemplate(SimpleLdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public void testLookup() { + String result = ldapTemplate.lookup("cn=Some Person,ou=company1,c=Sweden", new CnContextMapper()); + assertEquals("Some Person", result); + } + + public void testSearch() { + List cns = ldapTemplate.search("", "(&(objectclass=person)(sn=Person3))", new CnContextMapper()); + + assertEquals(1, cns.size()); + assertEquals("Some Person3", cns.get(0)); + } + + public void testModifyAttributes() { + DirContextOperations ctx = ldapTemplate.lookupContext("cn=Some Person,ou=company1,c=Sweden"); + + ctx.setAttributeValue("description", "updated description"); + ctx.setAttributeValue("telephoneNumber", "0000001"); + + ldapTemplate.modifyAttributes(ctx); + + // verify that the data was properly updated. + ldapTemplate.lookup("cn=Some Person,ou=company1,c=Sweden", new ParameterizedContextMapper() { + public Object mapFromContext(Object ctx) { + DirContextAdapter adapter = (DirContextAdapter) ctx; + assertEquals("updated description", adapter.getStringAttribute("description")); + assertEquals("0000001", adapter.getStringAttribute("telephoneNumber")); + return null; + } + }); + } + + private static class CnContextMapper implements ParameterizedContextMapper { + + public String mapFromContext(Object ctx) { + DirContextAdapter adapter = (DirContextAdapter) ctx; + + return adapter.getStringAttribute("cn"); + } + } + +} diff --git a/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/conf/ldap.properties b/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/conf/ldap.properties new file mode 100644 index 00000000..6789a99b --- /dev/null +++ b/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/conf/ldap.properties @@ -0,0 +1,4 @@ +urls=ldap://127.0.0.1:3901 +userDn=uid=admin,ou=system +password=secret +base=dc=jayway,dc=se diff --git a/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/conf/ldapTemplateTestContext.xml b/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/conf/ldapTemplateTestContext.xml new file mode 100644 index 00000000..ff552c0d --- /dev/null +++ b/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/conf/ldapTemplateTestContext.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/log4j.properties b/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/log4j.properties new file mode 100644 index 00000000..f57302e8 --- /dev/null +++ b/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/log4j.properties @@ -0,0 +1,9 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +#Enable debug logging +log4j.category.net.sf.ldaptemplate=INFO +log4j.category.net.sf.ldaptemplate.LdapTemplate=DEBUG diff --git a/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/setup_data.ldif b/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/setup_data.ldif new file mode 100644 index 00000000..885a8ca4 --- /dev/null +++ b/mvn-build/integration-tests/tiger-integration-tests/src/test/resources/setup_data.ldif @@ -0,0 +1,110 @@ +dn: ou=groups,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: groups + +dn: cn=ROLE_USER,ou=groups,dc=jayway,dc=se +objectclass: top +objectclass: groupOfUniqueNames +cn: ROLE_USER +uniqueMember: cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se +uniqueMember: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se +uniqueMember: cn=Some Person,ou=company1,c=Norway,dc=jayway,dc=se +uniqueMember: cn=Some Person,ou=company2,c=Sweden,dc=jayway,dc=se +uniqueMember: cn=Some Person3,ou=company1,c=Sweden,dc=jayway,dc=se + +dn: cn=ROLE_ADMIN,ou=groups,dc=jayway,dc=se +objectclass: top +objectclass: groupOfUniqueNames +cn: ROLE_ADMIN +uniqueMember: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se + +dn: c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: country +c: Sweden +description: The country of Sweden + +dn: c=Norway,dc=jayway,dc=se +objectclass: top +objectclass: country +c: Norway +description: The country of Norway + +dn: ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: company1 +description: First company in Sweden + +dn: ou=company2,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: company2 +description: Second company in Sweden + +dn: ou=company1,c=Norway,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: company1 +description: First company in Norway + +dn: cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person +userPassword: password +cn: Some Person +sn: Person +description: Sweden, Company1, Some Person +telephoneNumber: +46 555-123456 + +dn: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person2 +userPassword: password +cn: Some Person2 +sn: Person2 +description: Sweden, Company1, Some Person2 +telephoneNumber: +46 555-654321 + +dn: cn=Some Person3,ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person3 +userPassword: password +cn: Some Person3 +sn: Person3 +description: Sweden, Company1, Some Person3 +telephoneNumber: +46 555-123654 + +dn: cn=Some Person,ou=company2,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person4 +userPassword: password +cn: Some Person +sn: Person +description: Sweden, Company2, Some Person +telephoneNumber: +46 555-456321 + +dn: cn=Some Person+sn=Person,ou=company1,c=Norway,dc=jayway,dc=se +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.norwegian +userPassword: password +cn: Some Person +sn: Person +description: Norway, Company1, Some Person+Person +telephoneNumber: +45 555-654123 diff --git a/mvn-build/lib/ldapbp.jar b/mvn-build/lib/ldapbp.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6ce7d68f33337a5e0fd1225460408c769087691 GIT binary patch literal 49437 zcma&OW0Yo5vMrjnZCBcMR@%1h{L*HnZQHhOo0YchO5;|a?)UELe&d|+_WrlWieE8e z&KWaeF9m5(Ff<^jzppMv6Fty>d!T{9fMi8g1nDH@#2CINfPg@O6r>@c{&522U&j>w z$6&O-C;s1HSwT5TF;Qg|dReipg<0!BMl{IV&fV|Z7~G~WEBwLCY93B2isr|hVReal zkpzu%KLHn9T#Pl7PMcA(ScV&)dz67Sv5*qK4szan=SHDjsGj=S;pG7Tu=iPO_Z- zH_KKS!QZMsib7=3O5*Z)St8=39L%_+IANe;Hz)~?@#0Q|AQ)||mfcKo95m52NLF@e z2t)ir+9Eu?HdmmaE&H7M?m%{+KX@bxeY{*Tb~|FcdLJ?PX))E2?V#w=eg|mcr~UTV z_RZr<j47+d4mJ}%@-&b%zx!-4fWrXi~IMhu^oWH$;Fnz$<)}z(bCz2 z!O76ch5=ydWX$k?6Z`u=0xAFZKw(EifUF1`y|ImE;WxEYQdhN5AI3?7A?ww9Py2bt}92Vfz#h)%XM8TWmteYj_(`%0hIp8PbmHVa3sAxcPNJaIFKI|yZuiw#;K1~eDRCZHbz6u@GPC` z{BSHdODtHKWnpxNhfG7;Mh zxNYKIhdIx)Z>A5$+2iriAYf_kwN?7)XbsA+swYlX4OLL4NQzMathESdp=T#LsCOSg zm6SL1a6lcCT-lh*lSXSOq%E7HDEV|6W*On#dILtjvgjVCWLN=5uv~i)aMNV+e-K^Z z@mH(!%)_I_vW@Z$)@o4Mg7@TLc&yTD#wLP9WG7=VY>cJkXDKCAQoL8uyQOJ73q9k1 zF0w6jSZkQEx)4=lrdtTQWm!(nh&+gnM#n^!DI-}L55 C8~*0mdR_C9GsVUiOr&O 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

d{RnOk;R7dK8R%(z|zks8ne>Tp_s-&06)rMws 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 F`+sPZVnqM| literal 0 HcmV?d00001 diff --git a/mvn-build/pom.xml b/mvn-build/pom.xml new file mode 100644 index 00000000..415ef246 --- /dev/null +++ b/mvn-build/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + org.springframework.ldap + projects + 1.2.2-SNAPSHOT + + pom + Spring LDAP Master Build Project + + + core + tiger + test-support + integration-tests + + + + ${basedir}/src/docbkx + ${basedir}/target/site/guide + 2.0.8 + + + + + + + + diff --git a/mvn-build/test-support/pom.xml b/mvn-build/test-support/pom.xml new file mode 100644 index 00000000..1cc35b94 --- /dev/null +++ b/mvn-build/test-support/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + org.springframework.ldap + spring-ldap-test + Spring LDAP Automated Test Support + 1.2.2-SNAPSHOT + + + 2.0.8 + + + + + + maven-compiler-plugin + + 1.4 + 1.4 + + + + + + + org.springframework.ldap + spring-ldap + 1.2.2-SNAPSHOT + + + + org.apache.commons + commons-io + 1.3.2 + + + org.apache.directory.server + apacheds-server-main + 1.5.0 + + + org.slf4j + nlog4j + + + + + org.slf4j + slf4j-log4j12 + 1.0.1 + + + + org.springframework + spring-core + ${spring.version} + + + org.springframework + spring-beans + ${spring.version} + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-mock + ${spring.version} + + + log4j + log4j + 1.2.9 + + + junit + junit + 3.8.2 + + + diff --git a/mvn-build/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckAttributesMapper.java b/mvn-build/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckAttributesMapper.java new file mode 100644 index 00000000..43b73c42 --- /dev/null +++ b/mvn-build/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckAttributesMapper.java @@ -0,0 +1,68 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.test; + +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.AttributesMapper; + +import junit.framework.Assert; + +/** + * Dummy AttributesMapper for testing purposes to check that the received + * Attributes are the expected ones. + * + * @author Mattias Arthursson + */ +public class AttributeCheckAttributesMapper implements AttributesMapper { + private String[] expectedAttributes = new String[0]; + + private String[] expectedValues = new String[0];; + + private String[] absentAttributes = new String[0];; + + public Object mapFromAttributes(Attributes attributes) + throws NamingException { + Assert.assertEquals("Values and attributes need to have the same length ", + expectedAttributes.length, expectedValues.length); + for (int i = 0; i < expectedAttributes.length; i++) { + Attribute attribute = attributes.get(expectedAttributes[i]); + Assert.assertNotNull("Attribute " + expectedAttributes[i] + + " was not present", attribute); + Assert.assertEquals(expectedValues[i], attribute.get()); + } + + for (int i = 0; i < absentAttributes.length; i++) { + Assert.assertNull(attributes.get(absentAttributes[i])); + } + + return null; + } + + public void setAbsentAttributes(String[] absentAttributes) { + this.absentAttributes = absentAttributes; + } + + public void setExpectedAttributes(String[] expectedAttributes) { + this.expectedAttributes = expectedAttributes; + } + + public void setExpectedValues(String[] expectedValues) { + this.expectedValues = expectedValues; + } +} \ No newline at end of file diff --git a/mvn-build/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckContextMapper.java b/mvn-build/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckContextMapper.java new file mode 100644 index 00000000..a12391c4 --- /dev/null +++ b/mvn-build/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckContextMapper.java @@ -0,0 +1,66 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.test; + +import junit.framework.Assert; + +import org.springframework.ldap.core.ContextMapper; +import org.springframework.ldap.core.DirContextAdapter; + +/** + * Dummy ContextMapper for testing purposes to check that the received + * Attributes are the expected ones. + * + * @author Mattias Arthursson + */ +public class AttributeCheckContextMapper implements ContextMapper { + private String[] expectedAttributes = new String[0]; + + private String[] expectedValues = new String[0]; + + private String[] absentAttributes = new String[0]; + + public Object mapFromContext(Object ctx) { + DirContextAdapter adapter = (DirContextAdapter) ctx; + Assert.assertEquals("Values and attributes need to have the same length ", + expectedAttributes.length, expectedValues.length); + for (int i = 0; i < expectedAttributes.length; i++) { + String attributeValue = adapter + .getStringAttribute(expectedAttributes[i]); + Assert.assertNotNull("Attribute " + expectedAttributes[i] + + " was not present", attributeValue); + Assert.assertEquals(expectedValues[i], attributeValue); + } + + for (int i = 0; i < absentAttributes.length; i++) { + Assert.assertNull(adapter.getStringAttribute(absentAttributes[i])); + } + + return null; + } + + public void setAbsentAttributes(String[] absentAttributes) { + this.absentAttributes = absentAttributes; + } + + public void setExpectedAttributes(String[] expectedAttributes) { + this.expectedAttributes = expectedAttributes; + } + + public void setExpectedValues(String[] expectedValues) { + this.expectedValues = expectedValues; + } +} \ No newline at end of file diff --git a/mvn-build/test-support/src/main/java/org/springframework/ldap/test/LdapTestUtils.java b/mvn-build/test-support/src/main/java/org/springframework/ldap/test/LdapTestUtils.java new file mode 100644 index 00000000..0386edac --- /dev/null +++ b/mvn-build/test-support/src/main/java/org/springframework/ldap/test/LdapTestUtils.java @@ -0,0 +1,205 @@ +package org.springframework.ldap.test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Hashtable; +import java.util.Properties; + +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.ContextNotEmptyException; +import javax.naming.InitialContext; +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + +import org.apache.commons.io.IOUtils; +import org.apache.directory.server.configuration.MutableServerStartupConfiguration; +import org.apache.directory.server.core.configuration.ShutdownConfiguration; +import org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration; +import org.apache.directory.server.jndi.ServerContextFactory; +import org.apache.directory.server.protocol.shared.store.LdifFileLoader; +import org.springframework.core.io.Resource; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.DistinguishedName; + +public class LdapTestUtils { + + /** + * Not to be instantiated. + */ + private LdapTestUtils() { + } + + public static DirContext startApacheDirectoryServer(int port, + String defaultPartitionSuffix, String defaultPartitionName) + throws NamingException { + + MutableServerStartupConfiguration cfg = new MutableServerStartupConfiguration(); + + // Determine an appropriate working directory + String tempDir = System.getProperty("java.io.tmpdir"); + cfg.setWorkingDirectory(new File(tempDir)); + + cfg.setLdapPort(port); + + MutableBTreePartitionConfiguration partitionConfiguration = new MutableBTreePartitionConfiguration(); + partitionConfiguration.setSuffix(defaultPartitionSuffix); + partitionConfiguration + .setContextEntry(getRootPartitionAttributes(defaultPartitionName)); + partitionConfiguration.setName(defaultPartitionName); + + cfg.setPartitionConfigurations(Collections + .singleton(partitionConfiguration)); + // Start the Server + + Hashtable env = createEnv(); + env.putAll(cfg.toJndiEnvironment()); + return new InitialDirContext(env); + } + + public static void destroyApacheDirectoryServer(String principal, + String credentials) throws Exception { + Properties env = new Properties(); + env.setProperty(Context.INITIAL_CONTEXT_FACTORY, + ServerContextFactory.class.getName()); + env.setProperty(Context.SECURITY_AUTHENTICATION, "simple"); + env.setProperty(Context.SECURITY_PRINCIPAL, principal); + env.setProperty(Context.SECURITY_CREDENTIALS, credentials); + + ShutdownConfiguration configuration = new ShutdownConfiguration(); + env.putAll(configuration.toJndiEnvironment()); + + new InitialContext(env); + } + + // + // public void cleanAndSetup(ContextSource contextSource, Resource ldifFile, + // DistinguishedName baseLdapPath) + // throws Exception { + // DirContext ctx = contextSource.getReadWriteContext(); + // + // // First of all, make sure the database is empty. + // Name startingPoint = null; + // + // // Different test cases have different base paths. This means that the + // // starting point will be different. + // if (baseLdapPath.size() != 0) { + // startingPoint = DistinguishedName.EMPTY_PATH; + // } else { + // startingPoint = new DistinguishedName(baseLdapPath); + // } + // + // try { + // clearSubContexts(ctx, startingPoint); + // // Load the ldif to the recently started server + // LdifFileLoader loader = new LdifFileLoader(ctx, ldifFile); + // loader.execute(); + // } finally { + // ctx.close(); + // } + // } + // + + public static void clearSubContexts(ContextSource contextSource, Name name) + throws NamingException { + DirContext ctx = null; + try { + ctx = contextSource.getReadWriteContext(); + clearSubContexts(ctx, name); + } finally { + ctx.close(); + } + } + + public static void clearSubContexts(DirContext ctx, Name name) + throws NamingException { + + NamingEnumeration enumeration = null; + try { + enumeration = ctx.listBindings(name); + while (enumeration.hasMore()) { + Binding element = (Binding) enumeration.next(); + DistinguishedName childName = new DistinguishedName(element + .getName()); + childName.prepend((DistinguishedName) name); + + try { + ctx.destroySubcontext(childName); + } catch (ContextNotEmptyException e) { + clearSubContexts(ctx, childName); + ctx.destroySubcontext(childName); + } + } + } catch (NamingException e) { + e.printStackTrace(); + } finally { + try { + enumeration.close(); + } catch (Exception e) { + // Never mind this + } + } + } + + public static void loadLdif(ContextSource contextSource, Resource ldifFile) + throws IOException { + DirContext context = contextSource.getReadWriteContext(); + try { + loadLdif(context, ldifFile); + } finally { + try { + context.close(); + } catch (Exception e) { + // This is not the exception we are interested in. + } + } + } + + private static void loadLdif(DirContext context, Resource ldifFile) + throws IOException { + File tempFile = File.createTempFile("spring_ldap_test", ".ldif"); + InputStream inputStream = ldifFile.getInputStream(); + + IOUtils.copy(inputStream, new FileOutputStream(tempFile)); + LdifFileLoader fileLoader = new LdifFileLoader(context, tempFile + .getAbsolutePath()); + fileLoader.execute(); + } + + private static Hashtable createEnv() { + Hashtable env = new Properties(); + + env.put(Context.PROVIDER_URL, ""); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "org.apache.directory.server.jndi.ServerContextFactory"); + + env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system"); + env.put(Context.SECURITY_CREDENTIALS, "secret"); + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + + return env; + } + + private static Attributes getRootPartitionAttributes( + String defaultPartitionName) { + BasicAttributes attributes = new BasicAttributes(); + BasicAttribute objectClassAttribute = new BasicAttribute("objectClass"); + objectClassAttribute.add("top"); + objectClassAttribute.add("domain"); + objectClassAttribute.add("extensibleObject"); + attributes.put(objectClassAttribute); + attributes.put("dc", defaultPartitionName); + + return attributes; + } + +} diff --git a/mvn-build/test-support/src/main/java/org/springframework/ldap/test/TestContextSourceFactoryBean.java b/mvn-build/test-support/src/main/java/org/springframework/ldap/test/TestContextSourceFactoryBean.java new file mode 100644 index 00000000..318ae196 --- /dev/null +++ b/mvn-build/test-support/src/main/java/org/springframework/ldap/test/TestContextSourceFactoryBean.java @@ -0,0 +1,117 @@ +package org.springframework.ldap.test; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.core.io.Resource; +import org.springframework.ldap.core.AuthenticationSource; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.support.DefaultDirObjectFactory; +import org.springframework.ldap.core.support.LdapContextSource; + +public class TestContextSourceFactoryBean extends AbstractFactoryBean { + private int port; + + private String defaultPartitionSuffix; + + private String defaultPartitionName; + + private String principal; + + private String password; + + private boolean baseOnTarget = true; + + private Resource ldifFile; + + private Class dirObjectFactory = DefaultDirObjectFactory.class; + + private boolean pooled = true; + + private AuthenticationSource authenticationSource; + + public void setAuthenticationSource( + AuthenticationSource authenticationSource) { + this.authenticationSource = authenticationSource; + } + + public void setPooled(boolean pooled) { + this.pooled = pooled; + } + + public void setDirObjectFactory(Class dirObjectFactory) { + this.dirObjectFactory = dirObjectFactory; + } + + public void setLdifFile(Resource ldifFile) { + this.ldifFile = ldifFile; + } + + public void setBaseOnTarget(boolean baseOnTarget) { + this.baseOnTarget = baseOnTarget; + } + + public void setDefaultPartitionSuffix(String defaultPartitionSuffix) { + this.defaultPartitionSuffix = defaultPartitionSuffix; + } + + public void setPrincipal(String principal) { + this.principal = principal; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setDefaultPartitionName(String defaultPartitionName) { + this.defaultPartitionName = defaultPartitionName; + } + + public void setPort(int port) { + this.port = port; + } + + protected Object createInstance() throws Exception { + LdapTestUtils.startApacheDirectoryServer(port, defaultPartitionSuffix, + defaultPartitionName); + + LdapContextSource targetContextSource = new LdapContextSource(); + if (baseOnTarget) { + targetContextSource.setBase(defaultPartitionSuffix); + } + + targetContextSource.setUrl("ldap://localhost:" + port); + targetContextSource.setUserDn(principal); + targetContextSource.setPassword(password); + targetContextSource.setDirObjectFactory(dirObjectFactory); + targetContextSource.setPooled(pooled); + if (authenticationSource != null) { + targetContextSource.setAuthenticationSource(authenticationSource); + } + targetContextSource.afterPropertiesSet(); + + if (baseOnTarget) { + LdapTestUtils.clearSubContexts(targetContextSource, + DistinguishedName.EMPTY_PATH); + } else { + LdapTestUtils.clearSubContexts(targetContextSource, + new DistinguishedName(defaultPartitionSuffix)); + } + + if (ldifFile != null) { + LdapTestUtils.loadLdif(targetContextSource, ldifFile); + } + + return targetContextSource; + } + + public Class getObjectType() { + return ContextSource.class; + } + + protected void destroyInstance(Object instance) throws Exception { + super.destroyInstance(instance); + + LdapTestUtils.destroyApacheDirectoryServer(principal, password); + } +} diff --git a/mvn-build/tiger/pom.xml b/mvn-build/tiger/pom.xml new file mode 100644 index 00000000..f87f048f --- /dev/null +++ b/mvn-build/tiger/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + org.springframework.ldap + spring-ldap-tiger + Spring LDAP Java 5 support + 1.2.2-SNAPSHOT + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + + + + + + org.springframework.ldap + spring-ldap + 1.2.2-SNAPSHOT + + + junit + junit + 3.8.2 + test + + + diff --git a/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/AbstractParameterizedContextMapper.java b/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/AbstractParameterizedContextMapper.java new file mode 100644 index 00000000..4e56b02f --- /dev/null +++ b/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/AbstractParameterizedContextMapper.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.simple; + +import org.springframework.ldap.core.DirContextOperations; + +/** + * Abstract superclass that may be used instead of implementing + * {@link ParameterizedContextMapper} directly. Subclassing from this superclass, + * the supplied context will be automatically cast to + * DirContextOperations. Note that if you use your own + * DirObjectFactory, this implementation will fail with a + * ClassCastException. + * + * @author Mattias Arthursson + * + */ +public abstract class AbstractParameterizedContextMapper implements ParameterizedContextMapper { + + /* + * (non-Javadoc) + * + * @see org.springframework.ldap.core.ContextMapper#mapFromContext(java.lang.Object) + */ + public final T mapFromContext(Object ctx) { + return doMapFromContext((DirContextOperations) ctx); + } + + /** + * Map a single DirContextOperation to an object. The + * supplied instance is the object supplied to + * {@link #mapFromContext(Object)} cast to a + * DirContextOperations. + * + * @param ctx the context to map to an object. + * @return an object built from the data in the context. + */ + protected abstract T doMapFromContext(DirContextOperations ctx); + +} diff --git a/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/ParameterizedContextMapper.java b/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/ParameterizedContextMapper.java new file mode 100644 index 00000000..f33f28bb --- /dev/null +++ b/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/ParameterizedContextMapper.java @@ -0,0 +1,26 @@ +package org.springframework.ldap.core.simple; + +import javax.naming.Binding; +import javax.naming.directory.SearchResult; + +import org.springframework.ldap.core.ContextMapper; + +/** + * Extension of the {@link ContextMapper} interface. Uses Java 5 covariant + * return types to override the return type of the + * {@link #mapFromContext(Object)} method to be the type parameter T. + * + * @param + */ +public interface ParameterizedContextMapper extends ContextMapper { + + /** + * Map a single LDAP Context to an object. The supplied Object + * ctx is the object from a single {@link SearchResult}, + * {@link Binding}, or a lookup operation. + * + * @param ctx the context to map to an object. + * @return an object built from the data in the context. + */ + public T mapFromContext(Object ctx); +} diff --git a/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/SimpleLdapOperations.java b/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/SimpleLdapOperations.java new file mode 100644 index 00000000..294d390c --- /dev/null +++ b/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/SimpleLdapOperations.java @@ -0,0 +1,142 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.simple; + +import java.util.List; + +import javax.naming.directory.Attributes; +import javax.naming.directory.SearchControls; + +import org.springframework.ldap.NamingException; +import org.springframework.ldap.core.DirContextOperations; +import org.springframework.ldap.core.DirContextProcessor; +import org.springframework.ldap.core.LdapOperations; + +/** + * LDAP operations interface usable on Java 5 and above, exposing a set of + * common LDAP operations. + * + * @author Mattias Arthursson + */ +public interface SimpleLdapOperations { + + /** + * Get the wrapped LdapOperations instance. + * + * @return the wrapped LdapOperations instance. + * @throws NamingException if any error occurs. + */ + LdapOperations getLdapOperations(); + + /** + * Search for a List of type T using the supplied filter and link + * ParametrizedContextMapper. + * + * @param base Base DN relative to the base of the ContextSource - where to + * start the search. + * @param filter Search filter. + * @param mapper the Mapper to supply all results to. + * @return a List of type T containing objects for all entries found, as + * mapped by the ParametrizedContextMapper. + * @throws NamingException if any error occurs. + */ + List search(String base, String filter, ParameterizedContextMapper mapper); + + /** + * Search for a List of type T using the supplied filter, SearchControls, + * DirContextProcessor and ParametrizedContextMapper. + * + * @param base Base DN relative to the base of the ContextSource - where to + * start the search. + * @param filter Search filter. + * @param controls the SearchControls. Make sure that the returningObjFlag + * is set to true. + * @param mapper the Mapper to supply all results to. + * @param processor the DirContextProcessor to be used for applying pre/post + * processing on the DirContext instance. + * @return a List of type T containing objects for all entries found, as + * mapped by the ParametrizedContextMapper. + * @throws NamingException if any error occurs. + */ + List search(String base, String filter, SearchControls controls, ParameterizedContextMapper mapper, + DirContextProcessor processor); + + /** + * Perform a lookup of the specified DN and map the result using the mapper. + * + * @param dn the Distinguished Name to look up. + * @param mapper the mapper to use. + * @return the mapped object, as received by the ParametrizedContextMapper. + * @throws NamingException if any error occurs. + */ + T lookup(String dn, ParameterizedContextMapper mapper); + + /** + * Look up the specified DN, and automatically cast it to a + * {@link DirContextOperations} instance. + * + * @param dn The Distinguished Name of the entry to look up. + * @return A {@link DirContextOperations} instance constructed from the + * found entry. + * @throws NamingException if any error occurs. + */ + DirContextOperations lookupContext(String dn); + + /** + * Create an entry in the LDAP tree. The attributes used to create the entry + * are either retrieved from the obj parameter or the + * attributes parameter (or both). One of these parameters + * may be null but not both. + * + * @param dn The distinguished name to bind the object and attributes to. + * @param obj The object to bind, may be null. Typically a DirContext + * implementation. + * @param attributes The attributes to bind, may be null. + * @throws NamingException if any error occurs. + */ + void bind(String dn, Object obj, Attributes attributes); + + /** + * Remove an entry from the LDAP tree. The entry must not have any children. + * + * @param dn The distinguished name to unbind. + * @throws NamingException if any error occurs. + */ + void unbind(String dn); + + /** + * Modify the Attributes of the entry corresponding to the supplied + * {@link DirContextOperations} instance. The instance should have been + * received from the {@link #lookupContext(String)} operation, and then modified to + * match the current state of the matching domain object, e.g.: + * + *

+	 * public void update(Person person) {
+	 * 	DirContextOperations ctx = simpleLdapOperations.lookup(person.getDn());
+	 * 
+	 * 	ctx.setAttributeValue("description", person.getDescription());
+	 * 	ctx.setAttributeValue("telephoneNumber", person.getPhone());
+	 * 	// More modifications here
+	 * 
+	 * 	simpleLdapOperations.modifyAttributes(ctx);
+	 * }
+	 * 
+ * + * @param ctx the entry to update in the LDAP tree. + * @throws NamingException if any error occurs. + */ + void modifyAttributes(DirContextOperations ctx); +} diff --git a/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/SimpleLdapTemplate.java b/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/SimpleLdapTemplate.java new file mode 100644 index 00000000..bd8107e9 --- /dev/null +++ b/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/SimpleLdapTemplate.java @@ -0,0 +1,137 @@ +/* + * Copyright 2005-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.core.simple; + +import java.util.List; + +import javax.naming.directory.Attributes; +import javax.naming.directory.SearchControls; + +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.DirContextOperations; +import org.springframework.ldap.core.DirContextProcessor; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.core.LdapTemplate; + +/** + * Java-5-based convenience wrapper for the classic LdapTemplate, adding some + * convenient shortcuts and taking advantage of Java 5 Generics. + * + * Use the {@link #getLdapOperations()} method if you need to invoke less + * commonly used template methods. + * + * @author Mattias Arthursson + */ +public class SimpleLdapTemplate implements SimpleLdapOperations { + + private LdapOperations ldapOperations; + + /** + * Constructs a new SimpleLdapTemplate instance wrapping the supplied + * LdapOperations instance. + * + * @param ldapOperations the LdapOperations instance to wrap. + */ + public SimpleLdapTemplate(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + /** + * Constructs a new SimpleLdapTemplate instance, automatically creating a + * wrapped LdapTemplate instance to work with. + * + * @param contextSource + */ + public SimpleLdapTemplate(ContextSource contextSource) { + this.ldapOperations = new LdapTemplate(contextSource); + } + + /* + * (non-Javadoc) + * @see org.springframework.ldap.core.simple.SimpleLdapOperations#getLdapOperations() + */ + public LdapOperations getLdapOperations() { + return ldapOperations; + } + + /* + * (non-Javadoc) + * @see org.springframework.ldap.core.simple.SimpleLdapOperations#lookup(java.lang.String, + * org.springframework.ldap.core.simple.ParametrizedContextMapper) + */ + @SuppressWarnings("unchecked") + public T lookup(String dn, ParameterizedContextMapper mapper) { + return (T) ldapOperations.lookup(dn, mapper); + } + + /* + * (non-Javadoc) + * @see org.springframework.ldap.core.simple.SimpleLdapOperations#search(java.lang.String, + * java.lang.String, + * org.springframework.ldap.core.simple.ParametrizedContextMapper) + */ + @SuppressWarnings("unchecked") + public List search(String base, String filter, ParameterizedContextMapper mapper) { + return ldapOperations.search(base, filter, mapper); + } + + /* + * (non-Javadoc) + * @see org.springframework.ldap.core.simple.SimpleLdapOperations#search(java.lang.String, + * java.lang.String, javax.naming.directory.SearchControls, + * org.springframework.ldap.core.simple.ParametrizedContextMapper, + * org.springframework.ldap.core.DirContextProcessor) + */ + @SuppressWarnings("unchecked") + public List search(String base, String filter, SearchControls controls, ParameterizedContextMapper mapper, + DirContextProcessor processor) { + return ldapOperations.search(base, filter, controls, mapper, processor); + } + + /* + * (non-Javadoc) + * @see org.springframework.ldap.core.simple.SimpleLdapOperations#lookup(java.lang.String) + */ + public DirContextOperations lookupContext(String dn) { + return ldapOperations.lookupContext(dn); + } + + /* + * (non-Javadoc) + * @see org.springframework.ldap.core.simple.SimpleLdapOperations#modifyAttributes(org.springframework.ldap.core.DirContextOperations) + */ + public void modifyAttributes(DirContextOperations ctx) { + ldapOperations.modifyAttributes(ctx); + } + + /* + * (non-Javadoc) + * @see org.springframework.ldap.core.simple.SimpleLdapOperations#bind(java.lang.String, + * java.lang.Object, javax.naming.directory.Attributes) + */ + public void bind(String dn, Object obj, Attributes attributes) { + ldapOperations.bind(dn, obj, attributes); + } + + /* + * (non-Javadoc) + * @see org.springframework.ldap.core.simple.SimpleLdapOperations#unbind(java.lang.String) + */ + public void unbind(String dn) { + ldapOperations.unbind(dn); + } + +} diff --git a/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/package.html b/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/package.html new file mode 100644 index 00000000..56f3b7ec --- /dev/null +++ b/mvn-build/tiger/src/main/java/org/springframework/ldap/core/simple/package.html @@ -0,0 +1,7 @@ + + + +Simplification layer over LdapTemplate for Java 5 and above. + + + diff --git a/mvn-build/tiger/src/main/java/overview.html b/mvn-build/tiger/src/main/java/overview.html new file mode 100644 index 00000000..28045ca7 --- /dev/null +++ b/mvn-build/tiger/src/main/java/overview.html @@ -0,0 +1,3 @@ + +This document is the API specification for the Java5-specific parts of the Spring LDAP Framework. + \ No newline at end of file

xIMXTzCz!PWHf;k0AKWz0 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 z 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 ZS8OItGvK?g2SfIM{e>0(*I)Sc{{bz;*^&SN literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/src/images/bannerR.gif b/mvn-build/core/docs/reference/src/images/bannerR.gif new file mode 100644 index 0000000000000000000000000000000000000000..548ab8848daf59ca783e1a21d131d7af7a71ff68 GIT binary patch literal 7949 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 literal 0 HcmV?d00001 diff --git a/mvn-build/core/docs/reference/src/images/logo.png b/mvn-build/core/docs/reference/src/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..31396b8a49cd65687bce18f95777cd610d195c5c GIT binary patch literal 23834 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