|
In the preceding sections, you have seen how to interact with a relational database. In this section, we briefly look at hierarchical databases that use LDAP, the Lightweight Directory Access Protocol. This section is adapted from Core JavaServer Faces by Geary and Horstmann [Sun Microsystems Press 2004]. LDAP is preferred over relational databases when the application data naturally follows a tree structure and when read operations greatly outnumber write operations. LDAP is most commonly used for the storage of directories that contain data such as user names, passwords, and permissions. NOTE
An LDAP database keeps all data in a tree structure, not in a set of tables as a relational database would. Each entry in the tree has the following:
Figure 4-9 shows an example of a directory tree. Figure 4-9. A directory treeHow to organize a directory tree, and what information to put in it, can be a matter of intense debate. We do not discuss the issues here. Instead, we simply assume that an organizational scheme has been established and that the directory has been populated with the relevant user data. Configuring an LDAP ServerYou have several options for running an LDAP server to try out the programs in this section. Here are the most common choices:
We give you brief instructions for configuring OpenLDAP. If you use another directory server, the basic steps are similar. If you use OpenLDAP, you need to edit the slapd.conf file before starting the LDAP server. (On Linux, the default location for the slapd.conf file is /usr/local/etc/openldap.) Edit the suffix entry in slapd.conf to match the sample data set. This entry specifies the distinguished name suffix for this server. It should read suffix "dc=mycompany,dc=com" You also need to configure an LDAP user with administrative rights to edit the directory data. In OpenLDAP, add these lines to slapd.conf: rootdn "cn=Manager,dc=mycompany,dc=com" rootpw secret You can now start the LDAP server. On Linux, run /usr/local/libexec/slapd. Next, populate the server with the sample data. Most LDAP servers allow the import of LDIF (Lightweight Directory Interchange Format) data. LDIF is a human-readable format that simply lists all directory entries, including their distinguished names, object classes, and attributes. Example 4-6 shows an LDIF file that describes our sample data. Example 4-6. sample.ldif1. # Define top-level entry 2. dn: dc=mycompany,dc=com 3. objectClass: dcObject 4. objectClass: organization 5. dc: mycompany 6. o: Core Java Team 7. 8. # Define an entry to contain people 9. # searches for users are based on this entry 10. dn: ou=people,dc=mycompany,dc=com 11. objectClass: organizationalUnit 12. ou: people 13. 14. # Define a user entry for John Q. Public 15. dn: uid=jqpublic,ou=people,dc=mycompany,dc=com 16. objectClass: person 17. objectClass: uidObject 18. uid: jqpublic 19. sn: Public 20. cn: John Q. Public 21. telephoneNumber: +1 408 555 0017 22. userPassword: wombat 23. 24. # Define a user entry for Jane Doe 25. dn: uid=jdoe,ou=people,dc=mycompany,dc=com 26. objectClass: person 27. objectClass: uidObject 28. uid: jdoe 29. sn: Doe 30. cn: Jane Doe 31. telephoneNumber: +1 408 555 0029 32. userPassword: heffalump 33. 34. # Define an entry to contain LDAP groups 35. # searches for roles are based on this entry 36. dn: ou=groups,dc=mycompany,dc=com 37. objectClass: organizationalUnit 38. ou: groups 39. 40. # Define an entry for the "techstaff" group 41. dn: cn=techstaff,ou=groups,dc=mycompany,dc=com 42. objectClass: groupOfUniqueNames 43. cn: techstaff 44. uniqueMember: uid=jdoe,ou=people,dc=mycompany,dc=com 45. 46. # Define an entry for the "staff" group 47. dn: cn=staff,ou=groups,dc=mycompany,dc=com 48. objectClass: groupOfUniqueNames 49. cn: staff 50. uniqueMember: uid=jqpublic,ou=people,dc=mycompany,dc=com 51. uniqueMember: uid=jdoe,ou=people,dc=mycompany,dc=com For example, with OpenLDAP, you use the ldapadd tool to add the data to the directory: ldapadd -f sample.ldif -x -D "cn=Manager,dc=mycompany,dc=com" -w secret Before proceeding, it is a good idea to double-check that the directory contains the data that you need. We suggest that you download Jarek Gawor's LDAP Browser\Editor from http://www-unix.mcs.anl.gov/~gawor/ldap/. This convenient Java program lets you browse the contents of any LDAP server. Launch the program and configure it with the following options:
Make sure the LDAP server has started, then connect. If everything is in order, you should see a directory tree similar to that shown in Figure 4-10. Figure 4-10. Inspecting an LDAP directory treeAccessing LDAP Directory InformationOnce your LDAP database is populated, connect to it with a Java program. You use the Java Naming and Directory Interface (JNDI), an interface that unifies various directory protocols. Start by getting a directory context to the LDAP directory, with the following incantation: Hashtable env = new Hashtable(); env.put(Context.SECURITY_PRINCIPAL, username); env.put(Context.SECURITY_CREDENTIALS, password); DirContext initial = new InitialDirContext(env); DirContext context = (DirContext) initial.lookup("ldap://localhost:389"); Here, we connect to the LDAP server at the local host. The port number 389 is the default LDAP port. If you connect to the LDAP database with an invalid user/password combination, an AuthenticationException is thrown. NOTE
To list the attributes of a given entry, specify its distinguished name and then use the getAttributes method: Attributes attrs = context.getAttributes("uid=jqpublic,ou=people,dc=mycompany,dc=com"); You can get a specific attribute with the get method, for example, Attribute commonNameAttribute = attrs.get("cn"); To enumerate all attributes, you use the NamingEnumeration class. The designers of this class felt that they too could improve on the standard Java iteration protocol, and they gave us this usage pattern: NamingEnumeration<? extends Attribute> attrEnum = attrs.getAll(); while (attrEnum.hasMore()) { Attribute attr = attrEnum.next(); String id = attr.getID(); . . . } Note the use of hasMore instead of hasNext. If you know that an attribute has a single value, you can call the get method to retrieve it: String commonName = (String) commonNameAttribute.get(); If an attribute can have multiple values, you need to use another NamingEnumeration to list them all: NamingEnumeration<?> valueEnum = attr.getAll(); while (valueEnum.hasMore()) { Object value = valueEnum.next(); . . . } NOTE
You now know how to query the directory for user data. Next, let us take up operations for modifying the directory contents. To add a new entry, gather the set of attributes in a BasicAttributes object. (The BasicAttributes class implements the Attributes interface.) Attributes attrs = new BasicAttributes(); attrs.put("uid", "alee"); attrs.put("sn", "Lee"); attrs.put("cn", "Amy Lee"); attrs.put("telephoneNumber", "+1 408 555 0033"); String password = "redqueen"; attrs.put("userPassword", password.getBytes()); // the following attribute has two values Attribute objclass = new BasicAttribute("objectClass"); objclass.add("uidObject"); objclass.add("person"); attrs.put(objclass); Then call the createSubcontext method. Provide the distinguished name of the new entry and the attribute set. context.createSubcontext("uid=alee,ou=people,dc=mycompany,dc=com", attrs); CAUTION
To remove an entry, call destroySubcontext: context.destroySubcontext("uid=jdoe,ou=people,dc=mycompany,dc=com"); Finally, you may want to edit the attributes of an existing entry. You call the method context.modifyAttributes(distinguishedName, flag, attrs); Here, flag is one of DirContext.ADD_ATTRIBUTE DirContext.REMOVE_ATTRIBUTE DirContext.REPLACE_ATTRIBUTE The attrs parameter contains a set of the attributes to be added, removed, or replaced. Conveniently, the BasicAttributes(String, Object) constructor constructs an attribute set with a single attribute. For example, context.modifyAttributes("uid=alee,ou=people,dc=mycompany,dc=com", DirContext.ADD_ATTRIBUTE, new BasicAttributes("title", "CTO")); context.modifyAttributes("uid=alee,ou=people,dc=mycompany,dc=com", DirContext.REMOVE_ATTRIBUTE, new BasicAttributes("telephoneNumber", "+1 408 555 0033")); context.modifyAttributes("uid=alee,ou=people,dc=mycompany,dc=com", DirContext.REPLACE_ATTRIBUTE, new BasicAttributes("userPassword", password.getBytes())); Finally, when you are done with a context, you should close it: context.close(); The program in Example 4-7 demonstrates how to access a hierarchical database through LDAP. The program lets you view, modify, and delete information in a database with the sample data in Example 4-6. Enter a uid into the text field and click the Find button to find an entry. If you edit the entry and click Save, your changes are saved. If you edited the uid field, a new entry is created. Otherwise, the existing entry is updated. You can also delete the entry by clicking the Delete button (see Figure 4-11). Figure 4-11. Accessing a hierarchical databaseThe following steps briefly describe the program.
You now know enough about directory operations to carry out the tasks that you will commonly need when working with LDAP directories. A good source for more advanced information is the JNDI tutorial at http://java.sun.com/products/jndi/tutorial. Example 4-7. LDAPTest.java1. import java.net.*; 2. import java.awt.*; 3. import java.awt.event.*; 4. import java.io.*; 5. import java.util.*; 6. import javax.naming.*; 7. import javax.naming.directory.*; 8. import javax.swing.*; 9. 10. /** 11. This program demonstrates access to a hierarchical database through LDAP 12. */ 13. public class LDAPTest 14. { 15. public static void main(String[] args) 16. { 17. JFrame frame = new LDAPFrame(); 18. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 19. frame.setVisible(true); 20. } 21. } 22. 23. /** 24. The frame that holds the data panel and the navigation buttons. 25. */ 26. class LDAPFrame extends JFrame 27. { 28. public LDAPFrame() 29. { 30. setTitle("LDAPTest"); 31. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 32. 33. JPanel northPanel = new JPanel(); 34. northPanel.setLayout(new java.awt.GridLayout(1, 2, 3, 1)); 35. northPanel.add(new JLabel("uid", SwingConstants.RIGHT)); 36. uidField = new JTextField(); 37. northPanel.add(uidField); 38. add(northPanel, BorderLayout.NORTH); 39. 40. JPanel buttonPanel = new JPanel(); 41. add(buttonPanel, BorderLayout.SOUTH); 42. 43. findButton = new JButton("Find"); 44. findButton.addActionListener(new 45. ActionListener() 46. { 47. public void actionPerformed(ActionEvent event) 48. { 49. findEntry(); 50. } 51. }); 52. buttonPanel.add(findButton); 53. 54. saveButton = new JButton("Save"); 55. saveButton.addActionListener(new 56. ActionListener() 57. { 58. public void actionPerformed(ActionEvent event) 59. { 60. saveEntry(); 61. } 62. }); 63. buttonPanel.add(saveButton); 64. 65. deleteButton = new JButton("Delete"); 66. deleteButton.addActionListener(new 67. ActionListener() 68. { 69. public void actionPerformed(ActionEvent event) 70. { 71. deleteEntry(); 72. } 73. }); 74. buttonPanel.add(deleteButton); 75. 76. addWindowListener(new 77. WindowAdapter() 78. { 79. public void windowClosing(WindowEvent event) 80. { 81. try 82. { 83. if (context != null) context.close(); 84. } 85. catch (NamingException e) 86. { 87. e.printStackTrace(); 88. } 89. } 90. }); 91. } 92. 93. /** 94. Finds the entry for the uid in the text field. 95. */ 96. public void findEntry() 97. { 98. try 99. { 100. if (scrollPane != null) remove(scrollPane); 101. String dn = "u,ou=people,dc=mycompany,dc=com"; 102. if (context == null) context = getContext(); 103. attrs = context.getAttributes(dn); 104. dataPanel = new DataPanel(attrs); 105. scrollPane = new JScrollPane(dataPanel); 106. add(scrollPane, BorderLayout.CENTER); 107. validate(); 108. uid = uidField.getText(); 109. } 110. catch (NamingException e) 111. { 112. JOptionPane.showMessageDialog(this, e); 113. } 114. catch (IOException e) 115. { 116. JOptionPane.showMessageDialog(this, e); 117. } 118. } 119. 120. /** 121. Saves the changes that the user made. 122. */ 123. public void saveEntry() 124. { 125. try 126. { 127. if (dataPanel == null) return; 128. if (context == null) context = getContext(); 129. if (uidField.getText().equals(uid)) // update existing entry 130. { 131. String dn = "u,ou=people,dc=mycompany,dc=com"; 132. Attributes editedAttrs = dataPanel.getEditedAttributes(); 133. NamingEnumeration<? extends Attribute> attrEnum = attrs.getAll(); 134. while (attrEnum.hasMore()) 135. { 136. Attribute attr = attrEnum.next(); 137. String id = attr.getID(); 138. Object value = attr.get(); 139. Attribute editedAttr = editedAttrs.get(id); 140. if (editedAttr != null && !attr.get().equals(editedAttr.get())) 141. context.modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, 142. new BasicAttributes(id, editedAttr.get())); 143. } 144. } 145. else // create new entry 146. { 147. String dn = "u,ou=people,dc=mycompany,dc=com"; 148. attrs = dataPanel.getEditedAttributes(); 149. Attribute objclass = new BasicAttribute("objectClass"); 150. objclass.add("uidObject"); 151. objclass.add("person"); 152. attrs.put(objclass); 153. attrs.put("uid", uidField.getText()); 154. context.createSubcontext(dn, attrs); 155. } 156. 157. findEntry(); 158. } 159. catch (NamingException e) 160. { 161. JOptionPane.showMessageDialog(LDAPFrame.this, e); 162. e.printStackTrace(); 163. } 164. catch (IOException e) 165. { 166. JOptionPane.showMessageDialog(LDAPFrame.this, e); 167. e.printStackTrace(); 168. } 169. } 170. 171. /** 172. Deletes the entry for the uid in the text field. 173. */ 174. public void deleteEntry() 175. { 176. try 177. { 178. String dn = "u,ou=people,dc=mycompany,dc=com"; 179. if (context == null) context = getContext(); 180. context.destroySubcontext(dn); 181. uidField.setText(""); 182. remove(scrollPane); 183. scrollPane = null; 184. repaint(); 185. } 186. catch (NamingException e) 187. { 188. JOptionPane.showMessageDialog(LDAPFrame.this, e); 189. e.printStackTrace(); 190. } 191. catch (IOException e) 192. { 193. JOptionPane.showMessageDialog(LDAPFrame.this, e); 194. e.printStackTrace(); 195. } 196. } 197. 198. /** 199. Gets a context from the properties specified in the file ldapserver.properties 200. @return the directory context 201. */ 202. public static DirContext getContext() 203. throws NamingException, IOException 204. { 205. Properties props = new Properties(); 206. FileInputStream in = new FileInputStream("ldapserver.properties"); 207. props.load(in); 208. in.close(); 209. 210. String url = props.getProperty("ldap.url"); 211. String username = props.getProperty("ldap.username"); 212. String password = props.getProperty("ldap.password"); 213. 214. Hashtable<String, String> env = new Hashtable<String, String>(); 215. env.put(Context.SECURITY_PRINCIPAL, username); 216. env.put(Context.SECURITY_CREDENTIALS, password); 217. DirContext initial = new InitialDirContext(env); 218. DirContext context = (DirContext) initial.lookup(url); 219. 220. return context; 221. } 222. 223. public static final int DEFAULT_WIDTH = 300; 224. public static final int DEFAULT_HEIGHT = 200; 225. 226. private JButton findButton; 227. private JButton saveButton; 228. private JButton deleteButton; 229. 230. private JTextField uidField; 231. private DataPanel dataPanel; 232. private Component scrollPane; 233. 234. private DirContext context; 235. private String uid; 236. private Attributes attrs; 237. } 238. 239. /** 240. This panel displays the contents of a result set. 241. */ 242. class DataPanel extends JPanel 243. { 244. /** 245. Constructs the data panel. 246. @param attributes the attributes of the given entry 247. */ 248. public DataPanel(Attributes attrs) throws NamingException 249. { 250. setLayout(new java.awt.GridLayout(0, 2, 3, 1)); 251. 252. NamingEnumeration<? extends Attribute> attrEnum = attrs.getAll(); 253. while (attrEnum.hasMore()) 254. { 255. Attribute attr = attrEnum.next(); 256. String id = attr.getID(); 257. 258. NamingEnumeration<?> valueEnum = attr.getAll(); 259. while (valueEnum.hasMore()) 260. { 261. Object value = valueEnum.next(); 262. if (id.equals("userPassword")) 263. value = new String((byte[]) value); 264. 265. JLabel idLabel = new JLabel(id, SwingConstants.RIGHT); 266. JTextField valueField = new JTextField("" + value); 267. if (id.equals("objectClass")) 268. valueField.setEditable(false); 269. if (!id.equals("uid")) 270. { 271. add(idLabel); 272. add(valueField); 273. } 274. } 275. } 276. } 277. 278. public Attributes getEditedAttributes() 279. { 280. Attributes attrs = new BasicAttributes(); 281. for (int i = 0; i < getComponentCount(); i += 2) 282. { 283. JLabel idLabel = (JLabel) getComponent(i); 284. JTextField valueField = (JTextField) getComponent(i + 1); 285. String id = idLabel.getText(); 286. String value = valueField.getText(); 287. if (id.equals("userPassword")) 288. attrs.put("userPassword", value.getBytes()); 289. else if (!id.equals("") && !id.equals("objectClass")) 290. attrs.put(id, value); 291. } 292. return attrs; 293. } 294. } javax.naming.directory.InitialDirContext 1.3
javax.naming.Context 1.3
javax.naming.directory.DirContext 1.3
javax.naming.directory.Attributes 1.3
javax.naming.directory.BasicAttributes 1.3
javax.naming.directory.Attribute 1.3
javax.naming.NamingEnumeration<T> 1.3
|
|