Download Complete Practical Spring LDAP: Using Enterprise Java-Based LDAP in Spring Data and Spring Framework 6 2nd Edition Varanasi Balaji PDF for All Chapters
Download Complete Practical Spring LDAP: Using Enterprise Java-Based LDAP in Spring Data and Spring Framework 6 2nd Edition Varanasi Balaji PDF for All Chapters
com
https://ebookmass.com/product/practical-spring-ldap-using-
enterprise-java-based-ldap-in-spring-data-and-spring-
framework-6-2nd-edition-varanasi-balaji/
OR CLICK HERE
DOWLOAD NOW
https://ebookmass.com/product/pro-spring-security-securing-spring-
framework-6-and-boot-3-based-java-applications-third-edition-massimo-
nardone/
ebookmass.com
https://ebookmass.com/product/beginning-spring-data-data-access-and-
persistence-for-spring-framework-6-and-boot-3-andres-sacco/
ebookmass.com
https://ebookmass.com/product/corn-book-for-young-folk-charles-
burgess-williams/
ebookmass.com
The Arctic: What Everyone Needs to Know Klaus Dodds
https://ebookmass.com/product/the-arctic-what-everyone-needs-to-know-
klaus-dodds/
ebookmass.com
https://ebookmass.com/product/video-production-disciplines-and-
techniques-12th-edition-ebook-pdf/
ebookmass.com
https://ebookmass.com/product/ios-architecture-patterns-mvc-mvp-mvvm-
viper-and-vip-in-swift-1st-edition-raul-ferrer-garcia/
ebookmass.com
https://ebookmass.com/product/oracles-always-win-willow-lake-
supernaturals-book-3-lori-ames/
ebookmass.com
https://ebookmass.com/product/new-technologies-for-emission-control-
in-marine-diesel-engines-kuwahara/
ebookmass.com
Solferino 21: Warfare, Civilians and Humanitarians in the
Twenty-First Century Hugo Slim
https://ebookmass.com/product/solferino-21-warfare-civilians-and-
humanitarians-in-the-twenty-first-century-hugo-slim/
ebookmass.com
Practical Spring
LDAP
Using Enterprise Java-Based LDAP in
Spring Data and Spring Framework 6
—
Second Edition
—
Balaji Varanasi
Andres Sacco
Practical Spring LDAP
Using Enterprise Java-Based
LDAP in Spring Data and Spring
Framework 6
Second Edition
Balaji Varanasi
Andres Sacco
Practical Spring LDAP: Using Enterprise Java-Based LDAP in Spring Data and
Spring Framework 6, Second Edition
Balaji Varanasi Andres Sacco
Salt Lake City, UT, USA Buenos Aires, Buenos Aires, Argentina
Acknowledgments���������������������������������������������������������������������������������������������������xv
Introduction�����������������������������������������������������������������������������������������������������������xvii
Updating an Entry����������������������������������������������������������������������������������������������������������������������� 28
Removing an Entry���������������������������������������������������������������������������������������������������������������������� 30
Searching Entries������������������������������������������������������������������������������������������������������������������������ 31
Check How the Operations Work������������������������������������������������������������������������������������������������� 33
JNDI Drawbacks�������������������������������������������������������������������������������������������������������������������������� 38
Summary������������������������������������������������������������������������������������������������������������������������������������ 38
vi
Table of Contents
vii
Table of Contents
OrFilter��������������������������������������������������������������������������������������������������������������������������������� 145
HardcodedFilter������������������������������������������������������������������������������������������������������������������� 146
WhitespaceWildcardsFilter�������������������������������������������������������������������������������������������������� 147
Handling Special Characters����������������������������������������������������������������������������������������������� 148
LDAP Query Builder Parameters����������������������������������������������������������������������������������������������� 148
Summary���������������������������������������������������������������������������������������������������������������������������������� 152
viii
Table of Contents
Index��������������������������������������������������������������������������������������������������������������������� 313
ix
About the Authors
Balaji Varanasi is a software development manager and technology entrepreneur. He
has over 13 years of experience architecting and developing Java/.NET applications and,
more recently, iPhone apps. During this period, he has worked in the areas of security,
web accessibility, search, and enterprise portals. He has a master’s degree in computer
science and serves as adjunct faculty, teaching programming and information system
courses. When not programming, he enjoys spending time with his lovely wife in Salt
Lake City, Utah.
xi
About the Technical Reviewer
Manuel Jordan is an autodidactic developer and researcher who enjoys learning new
technologies for his own experiments about creating new integrations among them.
Manuel won the 2010 Springy Award – Community Champion and Spring Champion
2013. In his little free time, he reads the Bible and composes music on his bass
and guitar.
You can reach him through his Twitter account, @dr_pompeii.
xiii
Acknowledgments
I would like to thank my family members and friends for their encouragement and
support during the writing of this book:
• My wife, Gisela, who was always patient when I spent long hours at
my computer desk working on this book
xv
Introduction
Practical Spring LDAP provides complete coverage of Spring LDAP, a framework
designed to take the pain out of LDAP programming. This book starts by explaining the
fundamental concepts of LDAP and showing the reader how to set up the development
environment. It then dives into Spring LDAP, analyzing the problems it is designed to
solve. After that, the book focuses on the practical aspects of unit testing and integration
testing with LDAP. An in-depth treatment of LDAP controls and Spring LDAP features,
such as Object-Directory Mapping and LDIF (LDAP Data Interchange Format) parsing,
follows this. Finally, it concludes with discussions on LDAP authentication and
connection pooling.
xvii
Introduction
Chapter 5 introduces the basics of JNDI object factories and uses these factories
for creating objects that are more meaningful to the application. You then examine
a complete Data Access Object (DAO) layer implementation using Spring LDAP and
object factories.
Chapter 6 covers LDAP search. This chapter begins with the underlying ideas of
LDAP search. I then introduce various Spring LDAP filters that make LDAP searching
easier. Finally, you look at creating a custom search filter to address situations where the
current set is insufficient.
Chapter 7 provides an in-depth overview of LDAP controls that can be used for
extending LDAP server functionality. Then it moves on to sorting and paging LDAP
results using sort and page controls.
Chapter 8 deals with Object-Directory Mapping (ODM), a feature in Spring LDAP. In
this chapter, you look at bridging the gap between the domain model and the directory
server. You then re-implement the DAO using ODM concepts.
Chapter 9 introduces the important ideas of transactions and transactional integrity
before analyzing the transaction abstractions provided by Spring Framework. Finally, it
takes a look at Spring LDAP’s compensating transaction support.
Chapter 10 starts with implementing authentication, the most common operation
against LDAP. It then deals with parsing LDIF files using another feature introduced
in Spring. I end the chapter by looking at the connection pooling support provided by
Spring LDAP.
Target Audience
Practical Spring LDAP is intended for developers interested in building Java/JEE
applications using LDAP. It also teaches techniques for creating unit/integration tests for
LDAP applications. The book assumes basic familiarity with Spring Framework; prior
exposure to LDAP is helpful but optional. Developers already familiar with Spring LDAP
will find best practices and examples to help them get the most out of the framework.
xviii
Introduction
P
rerequisites
You should install Java JDK1 21 or higher on your machine, Maven2 3.8.0 or higher, and
some IDE. Some options for the IDE could be Eclipse,3 IntelliJ IDEA,4 Visual Studio
Code,5 and others, but you can choose which is the best for you.
To reduce the complexity of installing all LDAP vendors on your machine, I
recommend you install Docker6 and use it to run each LDAP. The use and installation of
Docker are outside the scope of this book, but there are some tutorials7 or cheatsheet8
with the most common commands.
Note If you don’t have it installed on your machine, you can check Appendixes A,
B, and C, which have information about installing the different tools and loading the
information on LDAP.
After installing all the tools, you must check if they are correctly installed before
reading the different chapters.
In the case of Java, you need to run the following command:
% java -version
openjdk 21 2023-09-19
OpenJDK Runtime Environment (build 21+35-2513)
OpenJDK 64-Bit Server VM (build 21+35-2513, mixed mode, sharing)
After that, you need to check if the version of Maven is correct using this command:
% mvn --version
Apache Maven 3.9.1
Maven home: /usr/share/maven
1
https://jdk.java.net/
2
https://maven.apache.org/
3
https://www.eclipse.org/downloads
4
https://www.jetbrains.com/es-es/idea/
5
https://code.visualstudio.com/
6
https://www.docker.com/
7
https://docker-curriculum.com/
8
https://michaelhaar.dev/my-docker-compose-cheatsheet
xix
Introduction
Last, if you want to check whether Docker runs correctly on your machine, you can
do that using the following command:
% docker --version
Docker version 24.0.2, build cb74dfc
Remember that I mentioned that Docker is optional. It’s only recommended for
reducing the complexity of installing LDAP vendors on your machine.
Questions?
If you have any questions or suggestions, contact the author at sacco.andres@gmail.com.
xx
CHAPTER 1
Introduction to LDAP
We all deal with directories daily. We use a telephone directory to look up phone
numbers. When visiting a library, we use the library catalog to look up the books we
want to read. We use the file system directory with computers to store our files and
documents. Simply put, a directory is a repository of information. The information is
usually organized so that it can be retrieved easily.
Directories on a network are typically accessed using the client/server
communication model. Applications wanting to read or write data to a directory
communicate with specialized servers. The directory server performs a read or write
operation on the actual directory. Figure 1-1 shows this client/server interaction.
The communication between the directory server and client applications is usually
accomplished using standardized protocols. The Lightweight Directory Access Protocol
(LDAP) provides a standard protocol for communicating with a directory. The directory
servers that implement the LDAP protocol are usually called LDAP servers. The LDAP
1
© Balaji Varanasi and Andres Sacco 2023
B. Varanasi and A. Sacco, Practical Spring LDAP, https://doi.org/10.1007/979-8-8688-0002-3_1
Chapter 1 Introduction to LDAP
LDAP Overview
LDAP defines a message protocol used by directory clients and directory servers. LDAP
can be better understood by considering the following four models it is based on:
1
https://docs.oracle.com/javase/jndi/tutorial/ldap/models/x500.html
2
https://en.wikipedia.org/wiki/Write_Once_Read_Many
3
https://www.techtarget.com/searchstorage/definition/WORM-write-once-read-many
2
Chapter 1 Introduction to LDAP
Information Model
The basic unit of information stored in LDAP is an entry. Entries hold information about
real-world objects such as employees, servers, printers, and organizations. Each entry in
an LDAP directory comprises zero or more attributes. Attributes are key-value pairs that
hold information about the object the entry represents. The key portion of an attribute
is also called the attribute type and describes the information that can be stored in the
attribute. The value portion of the attribute contains the actual information. Table 1-1
shows a portion of an entry representing an employee. The left column in the entry
contains the attribute types, and the right column holds the attribute values.
4
https://www.oracle.com/in/database/what-is-a-relational-database/
5
https://www.ibm.com/products/db2
6
https://www.mysql.com/
7
https://www.postgresql.org/
8
https://cassandra.apache.org/_/index.html
9
https://www.mongodb.com/
3
Chapter 1 Introduction to LDAP
objectClass inetOrgPerson
givenName John
surname Smith
mail john@inflix.com
jsmith@inflix.com
mobile +1 801 100 1000
You will notice that the mail attribute has two values. Attributes that are allowed to
hold multiple values are called multivalued attributes. Single-valued attributes, on the
other hand, can only hold a single value. The LDAP specification does not guarantee the
order of the values in a multivalued attribute.
Each attribute type is associated with a syntax that dictates the format of the
data stored as an attribute value. For example, the mobile attribute type has a
telephoneNumber syntax. This forces the attribute to hold a string value with a length
between 1 and 32.
Additionally, the syntax also defines the attribute value behavior during search
operations. For example, the givenName attribute has the syntax DirectoryString. This
syntax enforces that only alphanumeric characters are allowed as values. Table 1-2 lists
some common attributes and their associated syntax description.
4
Chapter 1 Introduction to LDAP
The attributes in Table 1-2 are the most used for the developers and tools. Still, there
is a big list of other attributes depending on whether your vendor or the tool supports
it or not; for example, on the official web page10 of Microsoft, all the attributes are
supported for the Active Directory.
10
https://learn.microsoft.com/en-us/windows/win32/adschema/attributes-all
5
Chapter 1 Introduction to LDAP
Object Classes
In object-oriented languages, such as Java, we create a class and use it as a blueprint for
creating objects. The class defines the attributes/data (and behavior/methods) that these
instances can have. Similarly, object classes in LDAP determine the attributes an LDAP
entry can have. These object classes also define which attributes are mandatory and
which are optional. Every LDAP entry has a special attribute aptly named objectClass
that holds the object class it belongs to. Looking at the objectClass value in the employee
entry in Table 1-1, we can conclude that the entry belongs to the inetOrgPerson class.
Table 1-3 shows the required and optional attributes in a standard LDAP person object
class. The cn attribute holds the person’s common name, whereas the sn attribute holds
the person’s family name or surname.
sn description
telephoneNumber
cn userPassword
objectClass seeAlso
As in Java, an object class can extend other object classes. This inheritance will
allow the child object class to inherit parent class attributes. For example, the person
object class defines attributes such as common name and surname. The object class
inetOrgPerson extends the person class and thus inherits all the person’s attributes.
Additionally, inetOrgPerson defines attributes required for a person working in an
organization, such as departmentNumber and employeeNumber. One special object class,
namely, top, does not have any parents. All other object classes are decedents of top
and inherit all the attributes declared in it. The top object class includes the mandatory
objectClass attribute. Figure 1-2 shows the object inheritance.
6
Chapter 1 Introduction to LDAP
Most LDAP implementations come with standard object classes that can be used out of
the box. Table 1-4 lists some of these LDAP object classes and their commonly used attributes.
7
Chapter 1 Introduction to LDAP
On Oracle’s official website,11 you can find the list of LDAP object classes with
information about the Request for Comments (RFC), which added each object class.
Note In this book, you will see many references to RFCs. Request for Comments
(RFC) is a series of technical documents produced by the Internet Engineering Task
Force (IETF)12 that specify certain standards.
Each RFC has a number as part of the name and a series of sections with the
specification. An RFC could become obsolete to many other RFCs because a new
specification about certain technology appears, for example, a new version of LDAP.
On the RFC Editor web page,13 you can find information about RFCs and the
publication process of the new one.
Directory Schema
The LDAP directory schema is a set of rules determining the type of information stored
in a directory. Schemas can be considered packaging units and contain attribute type
definitions and object class definitions. The schema rules are verified before an entry
can be stored in LDAP. This schema checking ensures that the entry has all the required
attributes and contains no attributes not part of the schema. Figure 1-3 represents a
generic LDAP schema.
11
https://docs.oracle.com/cd/E24001_01/oid.1111/e10035/schema_objclass.htm
12
https://www.ietf.org/
13
https://www.rfc-editor.org/
8
Chapter 1 Introduction to LDAP
Like databases, directory schemas must be well designed to address issues like data
redundancy. Before implementing your schema, it is worth looking at publicly available
standard schemas. These standard schemas often contain all definitions to store the
required data and, more importantly, ensure interoperability across other directories.
Naming Model
The LDAP Naming model defines how entries are organized in a directory. It also
determines how a particular entry can be uniquely identified. The Naming model
recommends that entries be stored logically in a hierarchical fashion. This entry tree
is often called a directory information tree (DIT). Figure 1-4 provides an example of a
generic directory tree.
The tree’s root is usually referred to as the base or suffix of the directory. This entry
represents the organization that owns the directory. The format of suffixes can vary
from implementation to implementation, but generally, there are three recommended
approaches, as listed in Figure 1-5.
9
Chapter 1 Introduction to LDAP
The first recommended technique is to use the organization’s domain name as the
suffix. For example, if the organization’s domain name is example.com, the directory
suffix will be o=example.com. The second technique also uses the domain name, but
each name component is prepended with “dc=” and joined by commas. So the domain
name example.com would result in a suffix dc=example, dc=com. This technique is
proposed in RFC 224714 and is popular with Microsoft Active Directory. The third
technique uses the X.500 model and creates a suffix in the format o=organization name,
c=country code. In the United States, the suffix for the organization example would be
o=example, c=us.
The naming model also defines naming and uniquely identifying entries in a
directory. Entries with a common immediate parent are uniquely identified via their
relative distinguished name (RDN), also called distinguished name (DN). The RDN is
computed using one or more attribute/value pairs of the entry. In its simplest case, RDN
is usually of the form attribute-name = attribute value. Figure 1-6 provides a simplified
representation of an organization directory. Each person entry under ou=employees has
a unique uid. So the RDN for the first person entry would be uid=emp1, where emp1 is
the employee’s user id.
14
https://www.ietf.org/rfc/rfc2247.txt
10
Chapter 1 Introduction to LDAP
It is important to remember that RDN cannot be used to uniquely identify the entry
in the entire tree. However, this can be easily done by combining the RDNs of all the
entries in the path from the top of the tree to the entry. The result of this combination
is referred to as distinguished name (DN). In Figure 1-6, the DN for person 1 would be
uid=emp1, ou=employees, dc=example, dc=com. Since the DN is made by combining
RDNs, if an entry’s RDN changes, the DNs of that entry and all its child entries
also change.
There can be situations where a set of entries does not have a unique attribute. In
those scenarios, one option is to combine multiple attributes to create uniqueness. For
example, we can use the consumer’s common name and email address in the previous
directory as an RDN. Multivalued RDNs are represented by separating each attribute
pair with a +, like so:
cn = Balaji Varanasi + mail=balaji@inflinx.com
Functional Model
The LDAP Functional model describes the access and modification operations that can
be performed on the directory using the LDAP protocol. These operations fall into three
categories: query, update, and authentication.
11
Chapter 1 Introduction to LDAP
The query operations are used to search and retrieve information from a directory.
So whenever some information needs to be read, a search query must be constructed
and executed against LDAP. The search operation takes a starting point within DIT, the
search depth, and the attributes an entry must have for a match. In Chapter 6, you’ll
delve deep into searching and look at all the available options.
The update operations add, modify, delete, and rename directory entries. As the
name suggests, the add operation adds a new entry to the directory. This operation
requires the DN of the entry to be created and a set of attributes that constitute the
entry. The delete operation takes a fully qualified DN of the entry and deletes it from the
directory. The LDAP protocol allows only the leaf entries to be deleted. The modified
operation updates an existing entry. This operation takes the entry’s DN and a set of
modifications, such as adding a new attribute, updating a new one, or removing an
existing one. The rename operation can rename or move entries in a directory.
The authentication operations are used for connecting and ending sessions between
the client and the LDAP server. A bind operation initiates an LDAP session between the
client and server. Typically, this would result in an anonymous session. The client can
provide a DN and credentials to authenticate and create an authenticated session. On
the other hand, the unbind operation can be used to terminate an existing session and
disconnect from the server.
LDAP V3 introduced a framework for extending existing operations and adding new
ones without changing the protocol. You will take a look at these operations in Chapter 7.
Security Model
The LDAP Security model protects LDAP directory information from unauthorized
access. The model specifies which clients can access which parts of the directory and
what kinds of operations (search vs. update) are allowed.
The LDAP Security model is based on the client authenticating to the server.
As discussed earlier, this authentication process or bind operation involves the
client supplying a DN identifying itself and a password. An anonymous session is
established if the client does not provide a DN and password. RFC 282915 defines a
15
https://www.ietf.org/rfc/rfc2829.txt
12
Chapter 1 Introduction to LDAP
set of authentication methods that LDAP V3 servers must support. After successful
authentication, the access control models are consulted to determine whether the client
has sufficient privileges to do what is requested. Unfortunately, no standards exist for
access control models, and each vendor provides their implementations.
L DIF Format
The LDAP Data Interchange Format (LDIF) is a standard text-based format for
representing directory content and update requests. The LDIF format is defined in RFC
2849.16 LDIF files are typically used to export data from one directory server and import it
into another. It is also popular for archiving directory data and applying bulk updates to
a directory. You will use LDIF files to store your test data and refresh the directory server
between unit tests.
The basic format of an entry represented in LDIF is as follows:
#comment
dn: <distinguished name>
objectClass: <object class>
objectClass: <object class>
...
...
<attribute type>: <attribute value>
<attribute type>: <attribute value>
...
Lines in the LDIF file starting with a # character are considered comments. The dn
and at least one objectClass entry definition are considered required. Attributes are
represented as name/value pairs separated by a colon. Multiple attribute values are
specified in separate lines and will have the same attribute type. Since LDIF files are
purely text based, binary data needs to be Base64 encoded before it is stored as part of
the LDIF file.
16
https://www.ietf.org/rfc/rfc2849.txt
13
Chapter 1 Introduction to LDAP
Blank lines separate multiple entries in the same LDIF file. Listing 1-1 shows an
LDIF file with three employee entries. Notice that the cn attribute is multivalued and is
represented twice for each employee.
# Barbara’s Entry
dn: cn=Barbara J Jensen, dc=example, dc=com
# multi valued attribute
cn: Barbara J Jensen
cn: Babs Jensen
objectClass: personsn: Jensen
# Bjorn’s Entry
dn: cn=Bjorn J Jensen, dc=example, dc=com
cn: Bjorn J Jensen
cn: Bjorn Jensen
objectClass: person
sn: Jensen
# Base64 encoded JPEG photo
jpegPhoto:: /9j/4AAQSkZJRgABAAAAAQABAAD/2wBDABALD
A4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQ ERXRTc4UG1RV19iZ2hnPk1xeXB
keFxlZ2P/2wBDARESEhgVG
# Jennifer’s Entry
dn: cn=Jennifer J Jensen, dc=example, dc=com
cn: Jennifer J Jensen
cn: Jennifer Jensen
objectClass: person
sn: Jensen
L DAP History
LDAP was developed by Tim Howes, Steve Kille, and Wengyik Yeong to create a network
protocol to get data out. In 1993 appeared the first draft of the RFC 1487,17 which
contained the specification of LDAP based on the access of X.500.
17
https://datatracker.ietf.org/doc/html/rfc1487
14
Chapter 1 Introduction to LDAP
LDAP Vendors
LDAP gained wide support from various vendors. There has also been a strong open
source movement to produce LDAP servers. Table 1-5 outlines some of the popular
directory servers and include the information about which is the docker image to run
each of them with a small configuration.
18
www.umich.edu/~dirsvcs/ldap/ldap.html
19
https://datatracker.ietf.org/doc/html/rfc2251
20
https://datatracker.ietf.org/doc/html/rfc4519
15
Chapter 1 Introduction to LDAP
ApacheDS and OpenDJ are pure Java implementations of LDAP directories. You
will be using these two servers for unit and integration testing of the code throughout
this book.
Sample Application
Throughout this book, you will be working with a directory for a hypothetical book
library. I have chosen the library because the concept is universal and easy to grasp. A
library usually stores books and other multimedia that patrons can borrow. Libraries
also employ people to take care of daily library operations. To keep things manageable,
the directory will not store book information. A relational database is suitable for
recording book information. Figure 1-7 shows the LDAP directory tree for our library
application.
I have used the RFC 224714 convention in this directory tree to name the base entry.
The base entry has two organizational unit entries that hold the employees’ and patrons’
information. The ou=employees part of the tree will hold all the library employee
entries. The ou=patrons part of the tree will hold the library patron entries. Both
library employee and patron entries are of the type inetOrgPerson objectClass. Both
employees and patrons access library applications using their unique login id. Thus, the
uid attribute will be used as the RDN for entries.
17
Chapter 1 Introduction to LDAP
Summary
LDAP and applications that interact with LDAP have become a key part of every
enterprise today. This chapter covered the basics of the LDAP directory. You learned that
LDAP stores information as entries. Each entry is made up of attributes that are simply
key-value pairs. These entries can be accessed via their distinguished names. You also
saw that LDAP directories have schemas that determine the type of information that can
be stored.
The next chapter will look at communicating with an LDAP directory using Java
Naming and Directory Interface (JNDI). In the chapters following Chapter 2, you will
focus on using Spring LDAP for developing LDAP applications.
18
CHAPTER 2
1
https://en.wikipedia.org/wiki/Jakarta_Enterprise_Beans
2
https://www.ibm.com/docs/en/sdk-java-technology/8?topic=orb-corba
19
© Balaji Varanasi and Andres Sacco 2023
B. Varanasi and A. Sacco, Practical Spring LDAP, https://doi.org/10.1007/979-8-8688-0002-3_2
Chapter 2 Java Support for LDAP
The JNDI has been part of the standard JDK distribution since Java version 1.3
and has not moved to Jakarta like other packages. The API itself is spread across five
packages:
3
https://docs.oracle.com/en/java/javase/21/docs/api/java.naming/javax/naming/
package-summary.html
4
https://docs.oracle.com/en/java/javase/21/docs/api/java.naming/javax/naming/
directory/package-summary.html
5
https://docs.oracle.com/en/java/javase/21/docs/api/java.naming/javax/naming/event/
package-summary.html
6
https://docs.oracle.com/en/java/javase/21/docs/api/java.naming/javax/naming/ldap/
package-summary.html
7
https://docs.oracle.com/en/java/javase/21/docs/api/java.naming/javax/naming/ldap/
spi/package-summary.html
8
https://docs.oracle.com/en/java/javase/21/docs/api/java.naming/javax/naming/spi/
package-summary.html
20
Chapter 2 Java Support for LDAP
• Connect to LDAP
• Perform LDAP operations
Note Check if you have installed the correct version of Java on your machine.
To do this, look at Appendix A, which explains how to do it.
If you don’t have a test LDAP server available, please refer to the steps in Appendix
B for installing a local LDAP server or running it using Docker. The explanation
about how to install Docker and check if everything is okay appears in Appendix A.
This chapter aims to show how you can do different operations on LDAP using
Java without any framework. With this approach, you will see the complexity of
doing certain operations and writing many lines of code to do something simple
with the assistance of some framework like Spring LDAP.
Connect to LDAP
All the naming and directory operations using JNDI are performed relative to a context.
So the first step in using JNDI is to create a context that acts as a starting point on the
LDAP server. Such a context is referred to as an initial context. Once an initial context is
established, it can look up other contexts or add new objects.
The Context interface and InitialContext class in the javax.naming package can
be used for creating an initial naming context. Since we are dealing with a directory
here, we will use a more specific DirContext interface and its implementation
InitialDirContext. Both DirContext and InitialDirContext are available inside the
javax.naming.directory package. The directory context instances can be configured
21
Chapter 2 Java Support for LDAP
with properties that provide information about the LDAP server. The code in Listing 2-1
creates a context for an LDAP server running locally on port 1389.
Note In the following parts of the chapter, I will explain in detail the most relevant
parts to connect and execute some operations to LDAP.
By the end of this chapter, you will see the different outputs of the execution of the
different methods.
22
Chapter 2 Java Support for LDAP
Once a connection to the LDAP server is established, the application can identify
itself by providing authentication information. Contexts like the one created in
Listing 2-1, where authentication information is not provided, are called anonymous
contexts. LDAP servers usually have ACLs (access list controls) that restrict operations
and information to certain accounts. So it is very common in enterprise applications to
create and use authenticated contexts. Listing 2-2 provides an example of creating an
authenticated context. Notice that we have used three additional properties to provide
the binding credentials. The SECURITY_AUTHENTICATION property is set to simple,
indicating that we will use a plain text username and password for authentication.
Note On the declaration of the different properties, you can see the class
DirContext appear many times, which is not the best approach, so if you want
to reduce the duplicates, you can simplify using static imports. For example, you
can declare the static import like this: import static javax.naming.directory.
DirContext.INITIAL_CONTEXT_FACTORY, and on the declaration of the variable,
just use PROVIDER_URL.
Any problems occurring during the context creation will be reported as instances
of javax.naming.NamingException. NamingException is the superclass of all the
exceptions thrown by the JNDI API. This is a checked exception and must be handled
properly for the code to compile. Table 2-1 lists common exceptions we will likely
encounter during JNDI development.
23
Chapter 2 Java Support for LDAP
24
Random documents with unrelated
content Scribd suggests to you:
I learnt afterwards that as the Shah, if he sits himself, is obliged to
give seats to the ambassadors, he avoided it by not sitting down, but
lounged in the manner described. There was nothing particularly
striking in the room; it was much over-decorated, and in the most
barbarous taste; the carpets, however, were valuable.
The ambassadors now all gave the king a military salute, and so
did the suites and hangers-on. To this his Majesty returned a not
over-gracious nod. The king now addressed them in turn, and each
ambassador replied through his dragoman or Oriental secretary,
replying to the questions as to his sovereign’s health, and
congratulating the Shah on his festival. Mr. Alison presented a new
secretary, and introduced Colonel G⸺, who was favourably
received, and in fluent and graceful Persian he replied to the Shah’s
queries, and made somewhat of a speech on telegraph matters,
which was also graciously received, the Shah assenting frequently.
The king now unceremoniously left the room, and every one saluted.
We all hurried off to see the great ceremony of the public salaam.
We were ushered pell-mell into a room that commanded on one side
the court of audience, on the other the public square of Teheran. In
the former were drawn up in rows, according to their degree, all the
officers of state, all the governors of provinces, all the generals and
servants of the Crown, the secretaries of various departments, and
the foreign employés, among whom I saw Mr. D⸺ and one of the
signallers, of the Telegraph Department.
We were told that in a few moments the Shah would lighten their
countenances by appearing in an open balcony above our heads.
The royal “farrashes,” or carpet-spreaders, armed with long wands
of unpeeled boughs, who surrounded the courtyard, began to beat
the few unauthorized onlookers at the far corners, and on a sudden
the whole crowd bowed nearly to the ground—a ceremony in which
the unfortunate Mr. D⸺ had to join nolens volens. This told us that
the king had shown himself.
The prostration was repeated a second and a third time. Then the
Prime Minister, having his rod of office, with many bows, mumbled a
speech to his Majesty; to which the king replied in a few words in a
loud voice.
A priest in a green turban (being a Syud or descendant of the
prophet) now recited what was apparently a long prayer: a dress of
honour on a tray was immediately, by the king’s order, produced, and
placed on his shoulders; and this was no empty compliment, for I
was told by an experienced onlooker that the cloak was worth one
hundred pounds or more.
Then a poet recited an ode, and got also a dress of honour; and
then, at the royal command, men bearing trays of gold coin
distributed handfuls to the officials, the number and size of the
handful being in proportion to rank; the bigger people, who stood in
the front ranks, getting the larger and more numerous handfuls.
Even Mr. D⸺, who was in a back row, got some seventy kerans
(three pounds). The coins were gold, and very thin, and are instituted
for this special occasion; they are called “shahis,” which is, literally,
“king’s money,” and were worth some one shilling and eightpence
each. During the excitement and scramble that the distribution
occasioned, the king retired, and the orderly ranks of Government
servants became at once a seething crowd.
We lookers-on now crossed the room and stood at a balcony
which commanded the public square. This was kept clear by a
double line of soldiers all in new clothes for the occasion. The space
was occupied by dancers, buffoons, jugglers, wrestlers, sword-and-
buckler men, and owners of fighting sheep and bulls, with their
animals; while in front of the big pond or hauz, immediately below
our balcony, stood twenty wretched Jews in rags and tatters,
prepared to be thrust head over heels into the water for the royal
delectation.
The king’s farrashes kept up showers of good-humoured blows on
an equally good-humoured crowd at all the entrances. Not less than
fourteen to sixteen thousand people were present; all were on the
tiptoe of expectation.
Suddenly a cannon from among a battery in the square was
discharged, and the king appeared. The entire crowd bowed to the
ground three times; then the people shouted and cheered, the
dancers went through their antics, the buffoons began their jokes,
some forty pairs of wrestlers struggled for mastery, among whom
was the king’s giant, seven feet eight inches high; gymnasts threw
up and caught huge clubs, and showed feats of strength and skill;
the swordsmen engaged in cut and thrust, hacking each other’s
bucklers; the jugglers showed their sleight of hand; the fighting bulls
and sheep rushed at each other; the royal bands and the regimental
ones struck up different tunes; the zambūreks (or camel artillery)
discharged their little cannon; the Jews were cast into the tank, and
on coming out were again thrown in by the farrashes and
executioners; while the rest of the cannon fired away merrily in every
direction; the bulls got among the crowd, the women shrieked and
the men shouted.
Handfuls of gold coin were thrown to the various performers, for
which they violently scrambled; and amidst the smoke and cries the
king retired.
The royal salaam was over, and we struggled through the crowd
within the palace to our horses at the gate, and rode home through a
happy mob, having assisted at a great Persian festival.
I dined at the Russian, English, and French embassies several
times at Teheran. As the entertainments were European there is
nothing to be described.
CHAPTER V.
HAMADAN.
A few days after the great festival Major S⸺, who was going
down country, kindly allowed me to accompany him as far as
Hamadan. We started one afternoon, doing the two first stages by
sunset, and stopping at the post-house at Karneabad.
The weather was fine, the roads and horses good. I had by this
time learnt to ride by balance only, and acquired the art of remaining
in an upright position on my steed whenever he suddenly dropped as
if shot, instead of going over his head by the force of momentum.
The Major had a few tinned provisions, which it had been impossible
to get in a place like Tiflis, and with a roast fowl or two our
commissariat was well provided. The intense cold was over, and I
was glad to use my goggles to protect my eyes during the middle of
the day. We also never started before the light was good, which
made an immense difference in our comfort.
I had invested in a native bridle, the severe bit of which enabled
me thoroughly to control my horses, and, being the one they were
used to, did not keep them in the perpetual state of fret that the
European bit did. My saddle-bags, too, were well packed and exactly
the same weight, so that I never had to get down to put them level,
and they never annoyed the horse.
I had my rugs, four in number, and the same size, sewn together
down one side and at the bottom, so that whichever side I might
have to the draught, and of this there is always plenty, I could have
one blanket under me, three over me, and the sewn edge to the
wind, while, as the bottom was sewn up, the blankets could never
shift, and the open side could be always kept to the wall. This
arrangement, an original one, I have never altered, for in hot
weather, by lying on say three blankets, one only was over me.
There is, however, one thing that I soon found out in travelling. To
thoroughly rest oneself it is needful to, firstly, undress and wear a
night-jacket and pyjamas; and, secondly, to sleep in a sheet. The
addition to one’s comfort is immense, particularly in warm weather,
while the extra weight of a sheet is not worth considering. An air
pillow, too, is a great luxury.
I have been in the habit of no longer using a waterproof sheet to
keep my blankets dry, but of rolling them tightly up, and then
strapping them and cramming them into an india-rubber soldier’s
hold-all, which ensures a dry bed, and straps handily to the saddle.
This hold-all was the cause of a rather amusing adventure.
On coming home once on leave, in a great hurry, I had left Persia
with only my hold-all, having given my saddle-bags and road kit to
my servant. I had come direct from Tzaritzin on the Volga to
Boulogne without stopping, but had to wait some hours on the tidal
boat before she started. I stepped on board and asked one of the
men where the steward was.
“Oh, he ain’t aboard yet, mate.”
“Can you get me a wash?”
“Come along a’ me, mate.” The man took me down to what
seemed the fo’cassel, and placed a bucket of water before me.
I said, “Come, is this the accommodation you give your first-class
passengers?”
The man roared with laughter.
“No yer doant, mate, no yer doant. I never seed no first-class
passengers with luggage like that,” pointing to my hold-all; and it was
only on producing my coupon book, that the man could be
persuaded I was not a deck passenger, and to take me to the saloon
aft.
As I was covered with coal dust, and generally grimy—the
opportunities for washing being then not what they are now in Russia
and Germany—the hold-all had made the man sure that I was an
impostor.
We came in the afternoon of the third day into Hamadan, having
done the stages in fair time. The journey was without incident, save
that a string of antelopes crossed the road in broad daylight some
ten yards ahead of us. As they appeared so suddenly, we neither of
us thought of using our revolvers. Hamadan looked pretty as we
entered it, and was surrounded by apparently interminable gardens.
On turning a corner we came upon Captain Pierson, under whom I
was to serve, and of whose division I was in medical charge. He had
ridden out to meet us.
In the early days of the Persian Telegraph it was usual to ride out
with the departing, and to do the same to meet the coming guest.
This is the Persian custom of the “istikhbal,” or ceremonious riding
out to meet the new arrival; being a very important ceremony,
regulated by hard-and-fast rules: such as that the greater the
personage, the further must the welcomer travel; while the lesser the
welcomer, the further must he go. Thus, in the case of a new
governor of Shiraz, the king’s son, the big men rode out three
stages, the ex-governor one, while some actually went as far as
Abadeh, or seven days’ journey; but these were mostly merchants or
small people.
Great fuss and parade is made, the condition of the incomer being
denoted by the grandeur of this “istikhbal,” or procession of
welcome. In the case of official personages, soldiers, both horse and
foot, go out; led-horses also are sent simply for show, splendidly
caparisoned with Cashmere shawls or embroidered housings on the
saddles. And it is found necessary, in the case of the arrival of
ambassadors or envoys, such as that of Sir F. Goldsmid (when on
the duty of the definition of the Seistan boundary), to stipulate that a
proper istikhbal shall be sent out prior to the commissioners entering
a large town.
There is another ceremony, that of the “badraghah,” or riding out
with the departing guest. This, however, is not so formal, and is less
an act of ceremony than one of friendship; however, it is a
compliment that in both cases is much appreciated, especially when
shown by a European to a native.
Latterly the Europeans have almost given up this riding out, which
practically is a great nuisance to those riding at an unusual or
uncomfortable time, perhaps in the sun, and when the arrival of the
guest is very uncertain; it is, too, very annoying, when tired with a
rapid chupper, and having ridden many hours on end, to be put on a
very lively horse, ready to jump out of his skin with condition, and to
pull one’s arms off.
As we had got in sooner than was expected, and were only some
mile from Pierson’s house, we did not change our horses for the
fresh ones provided by him, and after many turns and twists between
high mud walls, we came to the house, and here my travels ended
for the time.
The courtyard was some twenty yards by thirty wide. A hauz or
tank ran the entire length, filled by a constant stream of running
water, and on either side of it was a long flower-bed sunk in the
stone pavement, about the same depth below it as the hauz was
elevated above.
On a level with the ground in the basement were the cellars and
servants’ quarters, and above this a platform ten feet from the
ground, some four yards broad, which extended the whole width of
the courtyard. This was covered by an enormous structure,
consisting of a roof some six feet thick, being painted wood mudded
over a yard deep; and then under it a hollow air-chamber, supported
on three huge wooden octagonal columns, likewise painted in red,
blue, and yellow. Behind and beneath this talár, or verandah, which
was some thirty feet from floor to ceiling, was a central room
(orūssēe), elaborately painted and gilt in the vilest taste, with a huge
window (which could be kept wide open in hot weather) of coloured
glass, in small panes four inches by seven. This was the dining and
reception room.
On either side of this orūssēe, and having the talár still in front of
them, was a smaller apartment. One was Pierson’s bedroom, the
other mine. Thus in front of the three rooms was a covered platform,
four yards by twenty. On this during the summer, save when the sun
was on it, we lived, and when the sun was high the rooms were kept
cool by the talár.
We soon sat down to a sumptuous dinner, and I tasted, for the first
time, Hamadan wine, of which I had heard many and different
opinions. It was a delicious pale, scented, straw-coloured wine, like a
light hock; rather too sweet, but apparently of no great strength. I
soon found, however, that in the latter idea I was much in error, for it
was a wine that went straight to the head, and remained there.
Delicious as it is, the fact of its newness—and it often will not
keep, a second summer generally turning it sour if in bottle—makes
it objectionable, for though it is light and delightful, especially when
iced, a headache surely follows even a third glass.
The natives, we found out in after years, are able to keep it in bulk,
and then the tendency to give an after headache goes away, but so
does the delicious flavour. In winter so cold is Hamadan, that the
wine, which is kept in huge jars holding two hundred maunds (or
eight hundred bottles), or even more, sunk half their depth in the
ground, has to be kept from freezing by making a hot-bed of
fermenting horse-dung around the upper part of these jars, and often
these means fail; for I have myself been present when blocks of
frozen wine have been chopped out of the jars for drinking; these
plans of storing wine only refer to Hamadan: in other Persian towns
the wine, as soon as it is cleared, is placed in carboys, holding from
six to twenty-four bottles.
It is sold in Hamadan in baghallis, or native bottles, holding about
a pint and a half. They are of the very thinnest glass, and very fragile
when empty. One of these bottlefuls costs about fourpence—at least
it did when I was in Hamadan in 1869.
In a couple of days Major S⸺ left on his way to Baghdad, and
Pierson insisted on my remaining his guest, which I was only too
glad to do, till I could get servants, etc. of my own.
The first thing, however, was to buy a horse, as I could not draw
my horse allowance from Government till I had really a horse of my
own, and the three pounds a month was, considering the smallness
of my pay, a consideration. Of course at that time I knew nothing
about horses, and was fortunate in having the advice of Pierson. As
soon as it was given out that I wanted horses there was a permanent
levée at our quarters of all the owners of the lame, the halt and the
blind, and their animals. These men, however, were all sent to the
right-about by Pierson, and at last a dealer came with four likely
young horses; these were examined and pronounced sound. On
their price being asked, one hundred tomans each was demanded. I
was disappointed, for this was exactly the sum (forty pounds) that I
was prepared to give for two horses. But I was reassured by Pierson,
who made me understand that that was always the price asked for
any beast worth having, and merely meant that the seller did not
mean to take less than one half the amount. I was told, too, that if
one wanted to buy a horse anywhere near its value some weeks
must be taken in the negotiation. The matter ended in Pierson’s
offering the dealer fifty tomans for two of the animals, and the man
leaving our courtyard in simulated indignation, declining even to
notice a bid so ridiculous. However, as Pierson said we had not seen
the last of him, I did not despair.
Next morning, on coming out to breakfast, I saw our horse-dealer
seated with the servants, and as Pierson put it, “They are settling the
amount of commission they are each to get, and this commission
they will have; ten per cent. is legitimate, more is robbery. So all we
have to do is to be very determined; if you can get any two of the
four animals for your limit you will do well, if not you must let them
go.”
Pierson now sent for his head-man and told him that “I was to
have two serviceable horses for forty pounds, and that I should not
pay a penny more; so, as he knew the amount of modakel (profit) he
and the rest of the servants could make, he had better do the best
he could for me, and that he (Pierson) would see that I was not done
as to quality.” The man cast up his eyes and retired.
While we were at breakfast a poor prince, Abu Saif Mirza, came,
and was invited to partake. Pierson told me that he was a very good
fellow indeed, and a grandson of Futteh-Ali Shah,[7] a former Shah
of Persia, but from the irregularity with which his very small pension
was paid, he had to live almost by his gun, and chance meals, such
as the present.
Of course I could not understand what he said, but he fully entered
into the difficulty as to finding me a horse. And as in Persia nothing
can be done without stratagem, he suggested on the spot a means
for bringing the dealer to his senses; it was deep, “deep as the deep
blue sea.” It was simply this: he would exhibit his horse to Pierson
and promise to send it for trial to-morrow, naming a price just about
its value, and “then you will see all will be well.”
No sooner was breakfast over than the prince’s horse was brought
into the courtyard, stripped and examined, and the suggested
arrangement made. As it happened I afterwards bought this very
horse for Pierson to make a wedding present of, but he would have
been more than I could manage at the time, being a spirited beast
and a puller. The Shahzadeh (prince) took his departure, promising
loudly to send his horse round in the morning.
No sooner was he gone than the nazir, or head-servant, presented
himself and delivered to Pierson an oration somewhat of this sort.
“May I represent to the service of the sahib, that it would be very
unwise to purchase the horse of the prince? he is not young” (he
was five years old), “he is gone in the wind” (he was quite sound),
“and his temper is awful; besides this I have reason to know that he
is worthless in every respect” (he was one of the best horses I ever
saw, and I knew him for ten years). “Of course to me it would make a
great difference, for the prince has indeed offered me a handsome
commission” (quite untrue), “while from this poor dealer not a
farthing can be wrung by the servants. No! he would rather die than
pay one farthing. So though the other servants are loath to let a sale
take place to my sahib’s friend, yet I, as an old servant, and looking
for a reward from my sahib for conduct so disinterested, have after
infinite trouble got the dealer to consent to a hundred and fifty
tomans for any two of his four horses.”
“Be off,” was the laconic reply of Pierson. “When we ride to-day, if
the dealer will sell for my price, let the horses be ready and I will see
them and ride them; if not he can go.”
The man sighed and replied: “Ah, I see, sahib, the prince has
laughed at your beard, and persuaded you to buy his worthless
brute. I can’t offer such terms to a respectable man like the dealer,
but I will give the message.”
I now saw the horse-dealer leave the courtyard with the air of an
injured man, and I feared I was as far off a purchase as ever. But
Pierson reassured me. I had plagued him to sell me one of his own
large stud which he wished to reduce, but he declined with a smile,
saying he never sold a horse to a friend unless he was a thorough
judge, and that as I knew nothing about horses he must decline, as I
might repent when too late; and though I pressed him a good deal,
he would not relent. Few men would have lost an opportunity to get
rid of beasts they did not require, but Pierson was a man in a
thousand.
At that time he had eight horses in his stable, all good and all
sound. He had named them after heathen gods, Jupiter, Pluto,
Saturn, Cupid, Hercules, etc. But his pet nag, Apollo—a grey he had
given one hundred and twenty tomans, or fifty pounds, for, an
enormous price in those days in Persia—had a few weeks before
caught his foot in a hole while galloping over turf; horse and man
came down with a crash; Pierson was insensible; and when he came
to himself he found, some four yards off, his favourite lying dead with
his neck broken.
He rode away on his groom’s horse, the man carrying his saddle
and bridle. On getting to the house he sent a gang to bury the poor
beast, but too late, for the villagers had taken off the skin and tail.
Pierson on telling me the story did it so pathetically, that he left off
with wet eyes, and I felt inclined to sob myself.
As we got ready for the afternoon ride, the horse-dealer and his
four horses appeared, and with a sigh he informed Pierson that he
accepted the terms, or nearly so. On getting out of the town the
horses were put through their paces. They were a big grey, with
enormous mane and tail, of not much breed, but in dealer’s
condition, and a well-shaped and strong-looking beast; an iron-grey,
who plunged and shied and was generally vicious, but really the
most valuable of the four; a fourteen-hand pure-bred Arab, with a
huge scar of a spear-wound a foot long on his shoulder, otherwise
perfect, of angelic temper, but small by the side of the Persian
horses, as all Arabs are; his muzzle almost touched his chest as he
arched his neck, and his action was very high, yet easy; he seemed
an aristocrat compared to the rest; his thin and fine mane and tail
were like silk—he, too, was five. A big, coarse, raking chestnut, that
took all the boy who rode him could do to hold him, rising four,
completed the list.
Pierson kindly rode them all, and with considerable fear I did the
same, save the lively grey, which I wisely acknowledged to be too
much for me. The big chestnut bolted with me, but I stuck on. The
other chestnut was all I could wish, fast, paces good, no tricks,
willing—but, then, the scar. I did not wish to buy him on that account,
but Pierson over-ruled me, and I took his advice; he told me that in
Persia a scar was nothing, that I could ride the horse in comfort and
safety, as he had no vices, and that whenever I wished to sell I
should lose very little. The raking chestnut, as a young horse,
Pierson told me was a speculation; he might turn out well, he might
not. And the grey—well, all I could get out of Pierson was, that “he
had a fine mane and tail,” which he certainly had, and that “he was
value, or nearly.” He was not a well-bred animal, and I liked him, I
fear, on account of the mane and tail; but he pulled. All were entire
horses.
Pierson wouldn’t let me buy the iron-grey, had I wanted to, as he
said he was dangerous, even to a good rider.
So the matter ended in my taking the chestnut for five hundred
and fifty kerans and the grey for six hundred and fifty. Pierson said
the prices ought to have been reversed. He was right. I had that
chestnut Arab ten years; he never was sick or sorry, and I never had
to strike or spur him; a pressure of the knee and a shake of the rein
would make him do his utmost. And he was a fast horse; small as he
was he carried my twelve stone comfortably, and as a ladies’ horse
he was perfect, having a beautiful mouth, while he followed like a
dog, and nothing startled him or made him shy. In the stable he was
quiet, save to a new-comer, on whom he always left his mark by a
bite on the neck, and then, having asserted his position, which was
afterwards never disputed, he was always friendly to stable
companions. He never kicked. I gave him away at last, when I left
Persia on leave.
Next morning the “poor prince” called and looked over my
purchases; he approved the chestnut, but shook his head at the
grey, saying he had “ableh,” or leprosy, and that in time he would
break down, pine, and die. The only sign he had was a pink patch
the size of a fourpenny-piece on his black muzzle. “Give him back,”
said the Prince.
“I can’t see anything wrong,” said Pierson. His mane and tail
decided me. I stuck to him, christening him “Salts”—the chestnut I
called “Senna.”
The custom in Persia is that, until a horse has been three nights
fed in the stable of a new master (unless specially stipulated to the
contrary before witnesses of respectability, or in writing) he may be
returned without giving any reason whatever, simply on the
purchaser repenting his bargain; this is often taken advantage of by
the buyer to return the animal in order to lower his price; the
manœuvre seldom succeeds, as the seller is prepared for it.
The European, if awake to his own interest, generally spends the
three days in giving the beast a good “bucketting” over ploughed
land, when, if there be any hidden defect, it comes out, and the
animal can be returned. We did this, but no fault showing itself, I paid
my one hundred and twenty tomans[8] (forty-eight pounds) and
concluded the purchase.
CHAPTER VI.
HAMADAN.
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
ebookmass.com