diff --git a/samples/article-spring20/.springBeans b/samples/article-spring20/.springBeans new file mode 100644 index 00000000..fcb9ecc0 --- /dev/null +++ b/samples/article-spring20/.springBeans @@ -0,0 +1,25 @@ + + + 1 + + + + + + + src/main/webapp/WEB-INF/applicationContext.xml + src/main/webapp/WEB-INF/basic-servlet.xml + src/test/resources/config/testContext.xml + + + + + true + false + + src/main/webapp/WEB-INF/applicationContext.xml + src/main/webapp/WEB-INF/basic-servlet.xml + + + + diff --git a/samples/article-spring20/pom.xml b/samples/article-spring20/pom.xml new file mode 100644 index 00000000..95a0c608 --- /dev/null +++ b/samples/article-spring20/pom.xml @@ -0,0 +1,189 @@ + + + + org.springframework.ldap + spring-ldap-parent + 1.3.0.CI-SNAPSHOT + + 4.0.0 + spring-ldap-article-spring20 + Spring LDAP Article Sample (Spring 2.0) + war + Example code that matches the article published on + java.net on April 18, 2006. Uses Spring 2.0, rather than 2.5 + + + + maven-compiler-plugin + + 1.5 + 1.5 + + + + org.mortbay.jetty + maven-jetty-plugin + + + + + + + org.springframework.ldap + spring-ldap-test + + + log4j + log4j + 1.2.9 + + + javax.servlet + jstl + 1.2 + + + org.springframework.ldap + spring-ldap-samples-utils + + + org.springframework + spring-test + + + + + org.springframework + spring-beans + 2.0.8 + + + org.springframework + spring-core + 2.0.8 + + + org.springframework + spring-dao + 2.0.8 + + + org.springframework + spring-beans + + + org.springframework + spring-core + + + + + org.springframework + spring-jdbc + 2.0.8 + + + org.springframework + spring-beans + + + org.springframework + spring-core + + + + + org.springframework + spring-context + 2.0.8 + + + org.springframework + spring-beans + + + org.springframework + spring-core + + + + + org.springframework + spring-web + 2.0.8 + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + org.springframework + spring-core + + + + + org.springframework + spring-support + 2.0.8 + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + org.springframework + spring-core + + + + + org.springframework + spring-webmvc + 2.0.8 + + + org.springframework + spring-web + + + org.springframework + spring-support + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + org.springframework + spring-core + + + + + javax.servlet + servlet-api + provided + + + + org.springframework + spring-mock + 2.0.8 + test + + + diff --git a/samples/article-spring20/readme.txt b/samples/article-spring20/readme.txt new file mode 100644 index 00000000..e603ea31 --- /dev/null +++ b/samples/article-spring20/readme.txt @@ -0,0 +1,21 @@ +Uses Spring 2.0. +Sample application demonstrating how to do the most basic stuff in Spring LDAP. +A very simple dao implementation is provided in +org.springframework.ldap.samples.article.dao.PersonDaoImpl +It demonstrates some basic operations using Spring LDAP. For reference purposes, +a corresponding implementation using ordinary Java LDAP/JNDI implementation is +available in TraditionalPersonDaoImpl. + +How to use: +----------- +The project is in a Maven build structure. Make sure you have installed the samples-utils artifact, as this will be +needed for this project to work. + +'mvn jetty:run' will start up a web server demonstrating the capabilities. The web application will be available +under http://localhost:8080/spring-ldap-person-article-spring20/ + +'mvn eclipse:eclipse' will construct an Eclipse project for you to use. Import that project into Eclipse using +File/Import/Existing Project, and select this directory. + +'mvn test' will run some integration tests that require the LDAP server to be running. It's recommended to run +'mvn jetty:run' from another terminal window before 'mvn test'. diff --git a/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/dao/PersonDao.java b/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/dao/PersonDao.java new file mode 100644 index 00000000..5bbd799d --- /dev/null +++ b/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/dao/PersonDao.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005-2008 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.samples.article.dao; + +import java.util.List; + +import org.springframework.ldap.samples.article.domain.Person; + + +/** + * Data Access Object interface for the Person entity. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public interface PersonDao { + void create(Person person); + + void update(Person person); + + void delete(Person person); + + List getAllPersonNames(); + + List findAll(); + + Person findByPrimaryKey(String country, + String company, String fullname); +} diff --git a/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/dao/PersonDaoImpl.java b/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/dao/PersonDaoImpl.java new file mode 100644 index 00000000..26ff3d0e --- /dev/null +++ b/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/dao/PersonDaoImpl.java @@ -0,0 +1,163 @@ +/* + * Copyright 2005-2008 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.samples.article.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.DirContextAdapter; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.filter.EqualsFilter; +import org.springframework.ldap.samples.article.domain.Person; + +/** + * Default implementation of PersonDao. This implementation uses + * DirContextAdapter for managing attribute values. It has been specified in the + * Spring Context that the DirObjectFactory should be used when creating objects + * from contexts, which defaults to creating DirContextAdapter objects. This + * means that we can use a ContextMapper to map from the found contexts to our + * domain objects. This is especially useful since we in this case have + * properties in our domain objects that depend on parts of the DN. + * + * We could have worked with Attributes and an AttributesMapper implementation + * instead, but working with Attributes is a bore and also, working with + * AttributesMapper objects (or, indeed Attributes) does not give us access to + * the distinguished name. However, we do use it in one method that only needs a + * single attribute: {@link #getAllPersonNames()}. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public class PersonDaoImpl implements PersonDao { + + private LdapTemplate ldapTemplate; + + /* + * @see PersonDao#create(Person) + */ + public void create(Person person) { + Name dn = buildDn(person); + DirContextAdapter context = new DirContextAdapter(dn); + mapToContext(person, context); + ldapTemplate.bind(dn, context, null); + } + + /* + * @see PersonDao#update(Person) + */ + public void update(Person person) { + Name dn = buildDn(person); + DirContextAdapter context = (DirContextAdapter) ldapTemplate.lookup(dn); + mapToContext(person, context); + ldapTemplate.modifyAttributes(dn, context.getModificationItems()); + } + + /* + * @see PersonDao#delete(Person) + */ + public void delete(Person person) { + ldapTemplate.unbind(buildDn(person)); + } + + /* + * @see PersonDao#getAllPersonNames() + */ + public List getAllPersonNames() { + EqualsFilter filter = new EqualsFilter("objectclass", "person"); + return ldapTemplate.search(DistinguishedName.EMPTY_PATH, filter.encode(), new AttributesMapper() { + public Object mapFromAttributes(Attributes attrs) throws NamingException { + return attrs.get("cn").get(); + } + }); + } + + /* + * @see PersonDao#findAll() + */ + public List findAll() { + EqualsFilter filter = new EqualsFilter("objectclass", "person"); + return ldapTemplate.search(DistinguishedName.EMPTY_PATH, filter.encode(), getContextMapper()); + } + + /* + * @see PersonDao#findByPrimaryKey(java.lang.String, java.lang.String, + * java.lang.String) + */ + public Person findByPrimaryKey(String country, String company, String fullname) { + DistinguishedName dn = buildDn(country, company, fullname); + return (Person) ldapTemplate.lookup(dn, getContextMapper()); + } + + private ContextMapper getContextMapper() { + return new PersonContextMapper(); + } + + private DistinguishedName buildDn(Person person) { + return buildDn(person.getCountry(), person.getCompany(), person.getFullName()); + } + + private DistinguishedName buildDn(String country, String company, String fullname) { + DistinguishedName dn = new DistinguishedName(); + dn.add("c", country); + dn.add("ou", company); + dn.add("cn", fullname); + return dn; + } + + private void mapToContext(Person person, DirContextAdapter context) { + context.setAttributeValues("objectclass", new String[] { "top", "person" }); + context.setAttributeValue("cn", person.getFullName()); + context.setAttributeValue("sn", person.getLastName()); + context.setAttributeValue("description", person.getDescription()); + context.setAttributeValue("telephoneNumber", person.getPhone()); + } + + /** + * Maps from DirContextAdapter to Person objects. A DN for a person will be + * of the form cn=[fullname],ou=[company],c=[country], so + * the values of these attributes must be extracted from the DN. For this, + * we use the DistinguishedName. + * + *Mattias Hellborg Arthurssonellborg Arthursson + * @author Ulrik Sandberg + */ + private static class PersonContextMapper implements ContextMapper { + + public Object mapFromContext(Object ctx) { + DirContextAdapter context = (DirContextAdapter) ctx; + DistinguishedName dn = new DistinguishedName(context.getDn()); + Person person = new Person(); + person.setCountry(dn.getLdapRdn(0).getComponent().getValue()); + person.setCompany(dn.getLdapRdn(1).getComponent().getValue()); + person.setFullName(context.getStringAttribute("cn")); + person.setLastName(context.getStringAttribute("sn")); + person.setDescription(context.getStringAttribute("description")); + person.setPhone(context.getStringAttribute("telephoneNumber")); + + return person; + } + } + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } +} diff --git a/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/dao/TraditionalPersonDaoImpl.java b/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/dao/TraditionalPersonDaoImpl.java new file mode 100644 index 00000000..9e47adb9 --- /dev/null +++ b/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/dao/TraditionalPersonDaoImpl.java @@ -0,0 +1,377 @@ +/* + * Copyright 2005-2008 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.samples.article.dao; + +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; + +import javax.naming.Context; +import javax.naming.NameNotFoundException; +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.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +import org.apache.commons.lang.StringUtils; +import org.springframework.ldap.samples.article.domain.Person; + +/** + * Traditional implementation of PersonDao. This implementation uses the basic + * JNDI interfaces and classes {@link DirContext}, {@link Attributes}, + * {@link Attribute}, and {@link NamingEnumeration}. The purpose is to + * contrast this implementation with that of {@link PersonDaoImpl}. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public class TraditionalPersonDaoImpl implements + PersonDao { + + private String userName; + + private String password; + + private String url; + + private String base; + + /* + * @see PersonDao#create(Person) + */ + public void create(Person person) { + DirContext ctx = createAuthenticatedContext(); + String dn = buildDn(person); + try { + Attributes attrs = getAttributesToBind(person); + ctx.bind(dn, null, attrs); + } catch (NamingException e) { + throw new RuntimeException(e); + } finally { + if (ctx != null) { + try { + ctx.close(); + } catch (Exception e) { + // Never mind this. + } + } + } + } + + /* + * @see PersonDao#update(Person) + */ + public void update(Person person) { + DirContext ctx = createAuthenticatedContext(); + String dn = buildDn(person); + try { + ctx + .rebind( + dn, null, + getAttributesToBind(person)); + } catch (NamingException e) { + + throw new RuntimeException(e); + } finally { + if (ctx != null) { + try { + ctx.close(); + } catch (Exception e) { + // Never mind this. + } + } + } + } + + /* + * @see PersonDao#delete(Person) + */ + public void delete(Person person) { + DirContext ctx = createAuthenticatedContext(); + String dn = buildDn(person); + try { + ctx.unbind(dn); + } catch (NamingException e) { + throw new RuntimeException(e); + } finally { + if (ctx != null) { + try { + ctx.close(); + } catch (Exception e) { + // Never mind this. + } + } + } + } + + /* + * @see PersonDao#getAllPersonNames() + */ + public List getAllPersonNames() { + DirContext ctx = createAnonymousContext(); + + 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 (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; + } + + /* + * @see PersonDao#findAll() + */ + public List findAll() { + DirContext ctx = createAnonymousContext(); + + 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(); + String dn = searchResult.getName(); + Attributes attributes = searchResult + .getAttributes(); + list.add(mapToPerson(dn, attributes)); + } + } 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; + } + + /* + * @see PersonDao#findByPrimaryKey(java.lang.String, java.lang.String, + * java.lang.String) + */ + public Person findByPrimaryKey(String country, + String company, String fullname) { + + DirContext ctx = createAnonymousContext(); + String dn = buildDn( + country, company, fullname); + try { + Attributes attributes = ctx + .getAttributes(dn); + return mapToPerson(dn, attributes); + } catch (NameNotFoundException e) { + throw new RuntimeException( + "Did not find entry with primary key '" + + dn + "'", e); + } catch (NamingException e) { + throw new RuntimeException(e); + } finally { + if (ctx != null) { + try { + ctx.close(); + } catch (Exception e) { + // Never mind this. + } + } + } + } + + private String buildDn(Person person) { + return buildDn(person.getCountry(), person + .getCompany(), person.getFullName()); + } + + private String buildDn(String country, String company, + String fullname) { + StringBuffer sb = new StringBuffer(); + sb.append("cn="); + sb.append(fullname); + sb.append(", "); + sb.append("ou="); + sb.append(company); + sb.append(", "); + sb.append("c="); + sb.append(country); + String dn = sb.toString(); + return dn; + } + + private DirContext createContext(Hashtable env) { + env.put( + Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + String tempUrl = createUrl(); + env.put(Context.PROVIDER_URL, tempUrl); + DirContext ctx; + try { + ctx = new InitialDirContext(env); + } catch (NamingException e) { + throw new RuntimeException(e); + } + return ctx; + } + + private DirContext createAnonymousContext() { + Hashtable env = new Hashtable(); + return createContext(env); + } + + private DirContext createAuthenticatedContext() { + Hashtable env = new Hashtable(); + env.put( + Context.SECURITY_AUTHENTICATION, + "simple"); + env.put( + Context.SECURITY_PRINCIPAL, userName); + env.put( + Context.SECURITY_CREDENTIALS, password); + return createContext(env); + } + + private Attributes getAttributesToBind( + Person person) { + Attributes attrs = new BasicAttributes(); + BasicAttribute ocattr = new BasicAttribute( + "objectclass"); + ocattr.add("top"); + ocattr.add("person"); + attrs.put(ocattr); + attrs.put("cn", person.getFullName()); + attrs.put("sn", person.getLastName()); + attrs.put("description", person + .getDescription()); + attrs.put("telephoneNumber", person + .getPhone()); + return attrs; + } + + private Person mapToPerson(String dn, + 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("description").get()); + person.setPhone((String) attributes.get( + "telephoneNumber").get()); + + // Remove any trailing spaces after comma + String cleanedDn = dn + .replaceAll(", *", ","); + + String countryMarker = ",c="; + int countryIndex = cleanedDn + .lastIndexOf(countryMarker); + + String companyMarker = ",ou="; + int companyIndex = cleanedDn + .lastIndexOf(companyMarker); + + String country = cleanedDn + .substring(countryIndex + + countryMarker.length()); + person.setCountry(country); + String company = cleanedDn.substring( + companyIndex + companyMarker.length(), + countryIndex); + person.setCompany(company); + return person; + } + + private String createUrl() { + String tempUrl = url; + if (!tempUrl.endsWith("/")) { + tempUrl += "/"; + } + if (StringUtils.isNotEmpty(base)) { + tempUrl += base; + } + return tempUrl; + } + + public void setUrl(String url) { + this.url = url; + } + + public void setBase(String base) { + this.base = base; + } + + public void setPassword(String credentials) { + this.password = credentials; + } + + public void setUserDn(String principal) { + this.userName = principal; + } +} diff --git a/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/domain/Person.java b/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/domain/Person.java new file mode 100644 index 00000000..d56d4afd --- /dev/null +++ b/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/domain/Person.java @@ -0,0 +1,104 @@ +/* + * Copyright 2005-2008 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.samples.article.domain; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; + +/** + * Simple class representing a single person. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public class Person { + private String fullName; + + private String lastName; + + private String description; + + private String country; + + private String company; + + 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 getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public boolean equals(Object obj) { + return EqualsBuilder.reflectionEquals( + this, obj); + } + + public int hashCode() { + return HashCodeBuilder + .reflectionHashCode(this); + } + + public String toString() { + return ToStringBuilder.reflectionToString( + this, ToStringStyle.MULTI_LINE_STYLE); + } +} diff --git a/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/web/DefaultController.java b/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/web/DefaultController.java new file mode 100644 index 00000000..71accf2c --- /dev/null +++ b/samples/article-spring20/src/main/java/org/springframework/ldap/samples/article/web/DefaultController.java @@ -0,0 +1,132 @@ +package org.springframework.ldap.samples.article.web; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.springframework.ldap.core.DirContextOperations; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.samples.article.dao.PersonDao; +import org.springframework.ldap.samples.article.domain.Person; +import org.springframework.ldap.samples.utils.HtmlRowLdapTreeVisitor; +import org.springframework.ldap.samples.utils.LdapTree; +import org.springframework.ldap.samples.utils.LdapTreeBuilder; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.multiaction.MultiActionController; + +/** + * Default controller. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public class DefaultController extends MultiActionController { + + private LdapTreeBuilder ldapTreeBuilder; + + public void setLdapTreeBuilder(LdapTreeBuilder ldapTreeBuilder) { + this.ldapTreeBuilder = ldapTreeBuilder; + } + + public void setPersonDao(PersonDao personDao) { + this.personDao = personDao; + } + + private PersonDao personDao; + + public ModelAndView welcomeHandler(HttpServletRequest request, HttpServletResponse response) { + return new ModelAndView("welcome"); + } + + public ModelAndView showTree(HttpServletRequest request, HttpServletResponse response) { + LdapTree ldapTree = ldapTreeBuilder.getLdapTree(DistinguishedName.EMPTY_PATH); + HtmlRowLdapTreeVisitor visitor = new PersonLinkHtmlRowLdapTreeVisitor(); + ldapTree.traverse(visitor); + return new ModelAndView("showTree", "rows", visitor.getRows()); + } + + public ModelAndView addPerson(HttpServletRequest request, HttpServletResponse response) { + Person person = getPerson(); + + personDao.create(person); + return showTree(request, response); + } + + public ModelAndView updatePhoneNumber(HttpServletRequest request, HttpServletResponse response) { + Person person = personDao.findByPrimaryKey("Sweden", "company1", "John Doe"); + person.setPhone(StringUtils.join(new String[] { person.getPhone(), "0" })); + + personDao.update(person); + return showTree(request, response); + } + + public ModelAndView removePerson(HttpServletRequest request, HttpServletResponse response) { + Person person = getPerson(); + + personDao.delete(person); + return showTree(request, response); + } + + public ModelAndView showPerson(HttpServletRequest request, HttpServletResponse response, Person query) { + String country = query.getCountry(); + String company = query.getCompany(); + String fullName = query.getFullName(); + Person person = personDao.findByPrimaryKey(country, company, fullName); + return new ModelAndView("showPerson", "person", person); + } + + private Person getPerson() { + Person person = new Person(); + person.setFullName("John Doe"); + person.setLastName("Doe"); + person.setCompany("company1"); + person.setCountry("Sweden"); + person.setDescription("Test user"); + return person; + } + + /** + * Generates appropriate links for person leaves in the tree. + * + * @author Mattias Hellborg Arthursson + */ + private static final class PersonLinkHtmlRowLdapTreeVisitor extends HtmlRowLdapTreeVisitor { + @Override + protected String getLinkForNode(DirContextOperations node) { + String[] objectClassValues = node.getStringAttributes("objectClass"); + if (containsValue(objectClassValues, "person")) { + DistinguishedName distinguishedName = (DistinguishedName) node.getDn(); + String country = encodeValue(distinguishedName.getValue("c")); + String company = encodeValue(distinguishedName.getValue("ou")); + String fullName = encodeValue(distinguishedName.getValue("cn")); + + return "showPerson.do?country=" + country + "&company=" + company + "&fullName=" + fullName; + } + else { + return super.getLinkForNode(node); + } + } + + private String encodeValue(String value) { + try { + return URLEncoder.encode(value, "UTF8"); + } + catch (UnsupportedEncodingException e) { + // Not supposed to happen + throw new RuntimeException("Unexpected encoding exception", e); + } + } + + private boolean containsValue(String[] values, String value) { + for (String oneValue : values) { + if (StringUtils.equals(oneValue, value)) { + return true; + } + } + return false; + } + } +} diff --git a/samples/article-spring20/src/main/java/overview.html b/samples/article-spring20/src/main/java/overview.html new file mode 100644 index 00000000..309641cf --- /dev/null +++ b/samples/article-spring20/src/main/java/overview.html @@ -0,0 +1,3 @@ + +This document is the API specification for the Spring LDAP Article sample. + \ No newline at end of file diff --git a/samples/article-spring20/src/main/resources/log4j.properties b/samples/article-spring20/src/main/resources/log4j.properties new file mode 100644 index 00000000..5ce7a3ee --- /dev/null +++ b/samples/article-spring20/src/main/resources/log4j.properties @@ -0,0 +1,7 @@ +log4j.rootCategory=INFO, 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 + +log4j.logger.org.apache.directory=ERROR \ No newline at end of file diff --git a/samples/article-spring20/src/main/webapp/WEB-INF/applicationContext.xml b/samples/article-spring20/src/main/webapp/WEB-INF/applicationContext.xml new file mode 100644 index 00000000..5331fea0 --- /dev/null +++ b/samples/article-spring20/src/main/webapp/WEB-INF/applicationContext.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/article-spring20/src/main/webapp/WEB-INF/basic-servlet.xml b/samples/article-spring20/src/main/webapp/WEB-INF/basic-servlet.xml new file mode 100644 index 00000000..8917ac4b --- /dev/null +++ b/samples/article-spring20/src/main/webapp/WEB-INF/basic-servlet.xml @@ -0,0 +1,35 @@ + + + + + +/welcome.do=welcome +/showTree.do=showTree +/addPerson.do=addPerson +/updatePhoneNumber.do=updatePhoneNumber +/removePerson.do=removePerson +/showPerson.do=showPerson + + + + + + + /*.do=defaultController + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/article-spring20/src/main/webapp/WEB-INF/jsp/showPerson.jsp b/samples/article-spring20/src/main/webapp/WEB-INF/jsp/showPerson.jsp new file mode 100755 index 00000000..4d25b21b --- /dev/null +++ b/samples/article-spring20/src/main/webapp/WEB-INF/jsp/showPerson.jsp @@ -0,0 +1,20 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> + + +Back +

+ +Full name: ${person.fullName} +
+LastName: ${person.lastName} +
+Description: ${person.description} +
+Country: ${person.country} +
+Company: ${person.company} +
+Phone: ${person.phone} +
+

+ diff --git a/samples/article-spring20/src/main/webapp/WEB-INF/jsp/showTree.jsp b/samples/article-spring20/src/main/webapp/WEB-INF/jsp/showTree.jsp new file mode 100644 index 00000000..80546b96 --- /dev/null +++ b/samples/article-spring20/src/main/webapp/WEB-INF/jsp/showTree.jsp @@ -0,0 +1,17 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> + + +

Operations

+

Clicking a link below performs the described operation which will be reflected in the LDAP tree below

+Add new test person 'John Doe' (only works once)
+Add a '0' to the phone number of test person (only works if the person has been created)
+Remove test person
+

+

Tree contents

+

Click a person row to see the attribute values (country and company rows do not have additional info)

+ + ${row} + +

+ + diff --git a/samples/article-spring20/src/main/webapp/WEB-INF/ldap.properties b/samples/article-spring20/src/main/webapp/WEB-INF/ldap.properties new file mode 100644 index 00000000..8843f1f1 --- /dev/null +++ b/samples/article-spring20/src/main/webapp/WEB-INF/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/samples/article-spring20/src/main/webapp/WEB-INF/setup_data.ldif b/samples/article-spring20/src/main/webapp/WEB-INF/setup_data.ldif new file mode 100644 index 00000000..e6d7dce6 --- /dev/null +++ b/samples/article-spring20/src/main/webapp/WEB-INF/setup_data.ldif @@ -0,0 +1,35 @@ +dn: c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: country +c: Sweden +description: The country of Sweden + +dn: ou=company1,c=Sweden,dc=jayway,dc=se +objectclass: top +objectclass: organizationalUnit +ou: company1 +description: First company in Sweden + +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 diff --git a/samples/article-spring20/src/main/webapp/WEB-INF/web.xml b/samples/article-spring20/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..3482f7a9 --- /dev/null +++ b/samples/article-spring20/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,34 @@ + + + + Spring LDAP Basic Example + + + org.springframework.web.context.ContextLoaderListener + + + + + basic + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/basic-servlet.xml + + 1 + + + + basic + *.do + + + + index.htm + + diff --git a/samples/article-spring20/src/main/webapp/index.htm b/samples/article-spring20/src/main/webapp/index.htm new file mode 100644 index 00000000..c4a3f6f3 --- /dev/null +++ b/samples/article-spring20/src/main/webapp/index.htm @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/article-spring20/src/test/java/org/springframework/ldap/samples/article/dao/AbstractPersonDaoIntegrationTest.java b/samples/article-spring20/src/test/java/org/springframework/ldap/samples/article/dao/AbstractPersonDaoIntegrationTest.java new file mode 100644 index 00000000..a682c53a --- /dev/null +++ b/samples/article-spring20/src/test/java/org/springframework/ldap/samples/article/dao/AbstractPersonDaoIntegrationTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2005-2008 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.samples.article.dao; + +import java.util.List; + +import org.springframework.ldap.NameNotFoundException; +import org.springframework.ldap.samples.article.dao.PersonDao; +import org.springframework.ldap.samples.article.domain.Person; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +/** + * Abstract base class for PersonDao integration tests. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public abstract class AbstractPersonDaoIntegrationTest + extends + AbstractDependencyInjectionSpringContextTests { + + protected Person person; + + protected PersonDao personDao; + + protected String[] getConfigLocations() { + return new String[] { "config/testContext.xml" }; + } + + protected void onSetUp() throws Exception { + super.onSetUp(); + person = new Person(); + person.setCountry("Sweden"); + person.setCompany("company1"); + person.setFullName("Some Person"); + person.setLastName("Person"); + person + .setDescription("Sweden, Company1, Some Person"); + person.setPhone("+46 555-123456"); + } + + protected void onTearDown() throws Exception { + super.onTearDown(); + person = null; + personDao = null; + } + + /** + * Having a single test method test create, update and delete is not exactly + * the ideal way of testing, since they depend on each other. A better way + * would be to separate the tests and load a test fixture before each + * operation, in order to guarantee the expected state every time. See the + * ldaptemplate-person sample for the correct way to do this. + */ + public void testCreateUpdateDelete() { + try { + person.setFullName("Another Person"); + personDao.create(person); + personDao.findByPrimaryKey( + "Sweden", "company1", + "Another Person"); + // if we got here, create succeeded + + person + .setDescription("Another description"); + personDao.update(person); + Person result = personDao + .findByPrimaryKey( + "Sweden", "company1", + "Another Person"); + assertEquals( + "Another description", result + .getDescription()); + } finally { + personDao.delete(person); + try { + personDao.findByPrimaryKey( + "Sweden", "company1", + "Another Person"); + fail("NameNotFoundException (when using Spring LDAP) or RuntimeException (when using traditional) expected"); + } catch (NameNotFoundException expected) { + // expected + } catch (RuntimeException expected) { + // expected + } + } + } + + public void testGetAllPersonNames() { + List result = personDao.getAllPersonNames(); + assertEquals(2, result.size()); + String first = (String) result.get(0); + assertEquals("Some Person", first); + } + + public void testFindAll() { + List result = personDao.findAll(); + assertEquals(2, result.size()); + Person first = (Person) result.get(0); + assertEquals("Some Person", first + .getFullName()); + } + + public void testFindByPrimaryKey() { + Person result = personDao.findByPrimaryKey( + "Sweden", "company1", "Some Person"); + assertEquals(person, result); + } +} diff --git a/samples/article-spring20/src/test/java/org/springframework/ldap/samples/article/dao/PersonDaoImplIntegrationTest.java b/samples/article-spring20/src/test/java/org/springframework/ldap/samples/article/dao/PersonDaoImplIntegrationTest.java new file mode 100644 index 00000000..928207fd --- /dev/null +++ b/samples/article-spring20/src/test/java/org/springframework/ldap/samples/article/dao/PersonDaoImplIntegrationTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2005-2008 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.samples.article.dao; + +import org.springframework.ldap.samples.article.dao.PersonDaoImpl; + +/** + * Integration tests for the PersonDaoImpl class. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public class PersonDaoImplIntegrationTest extends + AbstractPersonDaoIntegrationTest { + + public void setPersonDao( + PersonDaoImpl personDao) { + this.personDao = personDao; + } +} diff --git a/samples/article-spring20/src/test/java/org/springframework/ldap/samples/article/dao/TraditionalPersonDaoImplIntegrationTest.java b/samples/article-spring20/src/test/java/org/springframework/ldap/samples/article/dao/TraditionalPersonDaoImplIntegrationTest.java new file mode 100644 index 00000000..4cc677f9 --- /dev/null +++ b/samples/article-spring20/src/test/java/org/springframework/ldap/samples/article/dao/TraditionalPersonDaoImplIntegrationTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2008 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.samples.article.dao; + +/** + * Integration tests for the TraditionalPersonDaoImpl class. + * + * @author Mattias Hellborg Arthursson + * @author Ulrik Sandberg + */ +public class TraditionalPersonDaoImplIntegrationTest + extends AbstractPersonDaoIntegrationTest { + + public void setPersonDao( + TraditionalPersonDaoImpl personDao) { + this.personDao = personDao; + } +} diff --git a/samples/article-spring20/src/test/resources/config/ldap.properties b/samples/article-spring20/src/test/resources/config/ldap.properties new file mode 100644 index 00000000..8843f1f1 --- /dev/null +++ b/samples/article-spring20/src/test/resources/config/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/samples/article-spring20/src/test/resources/config/testContext.xml b/samples/article-spring20/src/test/resources/config/testContext.xml new file mode 100644 index 00000000..4a762506 --- /dev/null +++ b/samples/article-spring20/src/test/resources/config/testContext.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/pom.xml b/samples/pom.xml index 86bdd9cf..d51f71d9 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -16,6 +16,7 @@ samples-utils article + article-spring20 demos