Apache James 에서는 아래와 같은 명령어로 서버에 접근하여 데이터를 가져올 수 있다. ( Local 포함 )
$ sudo ./james-cli.sh -h <IP> -p <Port> listusers
그렇다면 저러한 명령어를 실행하는 클라이언트와 클라이언트로부터 전달된 명령어를 수행하는 서버가 있을 것이다.
처음에 예상했던바로는
서버에서 이러한 로그를 출력하고 있기 때문에, Netty 를 통해서 서버-클라이언트 구조를 수립하고 송/수신할 것 이라고 생각했다.
하지만 Netty 내부 어디에도 James-cli.sh 의 명령어인 listUsers, listDomains 와 같은것을 처리하는부분이 없었다.
그리하여 이러한 명령어를 수행하는곳이
james-cli.sh 에서 ServerCmd 란 사실을 찾게 되었고. 해당 클래스에 가면
아래 사진들과 같은 방식으로 명령어를 처리하고 있다.
여기서
case ADDUSER -> probe.addUser(~)
를 보면, probe 객체를 통해 명령어를 처리하고자 하고 있다.
Probe 객체가 뭘까?
private final JmxDataProbe probe;
private final JmxMailboxProbe mailboxProbe;
private final JmxQuotaProbe quotaProbe;
private final JmxSieveProbe sieveProbe;
private final JmxDropListProbe dropListProbe;
public ServerCmd(JmxDataProbe probe, JmxMailboxProbe mailboxProbe, JmxQuotaProbe quotaProbe, JmxSieveProbe sieveProbe,
JmxDropListProbe dropListProbe) {
this.probe = probe;
this.mailboxProbe = mailboxProbe;
this.quotaProbe = quotaProbe;
this.sieveProbe = sieveProbe;
this.dropListProbe = dropListProbe;
}
ServerCmd.java 일부..
아래의 사진처럼 JMXProbe 를 통해서
작업을 진행하고 있음을 확인할 수 있다.
그래서 JMXProbe 를 통해 어떠한 작업을 하니 살펴보니(아까 addUser)
public void addUser(String userName, String password) throws Exception {
try (Closeable closeable = buildMdc("addUser", userName)) {
usersRepositoryProxy.addUser(userName, password);
}
}
아래는 userRepositoryProxy 클래스의 내용물이다.
또 여기서 userRepository(interface)->UsersRepositoryImpl(Class) 에 접근하여 내용을 보면
@Override
public void addUser(Username username, String password) throws UsersRepositoryException {
ensureNoConflict(username);
assertValid(username);
usersDAO.addUser(username, password);
}
어이쿠? userDAO(interface)->public class JPAUsersDAO implements UsersDAO, Configurable 에 접근하고 또 여기서
@Override
public void addUser(Username username, String password) throws UsersRepositoryException {
Username lowerCasedUsername = Username.of(username.asString().toLowerCase(Locale.US));
if (contains(lowerCasedUsername)) {
throw new UsersRepositoryException(lowerCasedUsername.asString() + " already exists.");
}
EntityManager entityManager = entityManagerFactory.createEntityManager();
final EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
JPAUser user = new JPAUser(lowerCasedUsername.asString(), password, algo);
entityManager.persist(user);
transaction.commit();
} catch (PersistenceException e) {
LOGGER.debug("Failed to save user", e);
if (transaction.isActive()) {
transaction.rollback();
}
throw new UsersRepositoryException("Failed to add user" + username.asString(), e);
} finally {
EntityManagerUtils.safelyClose(entityManager);
}
}
이렇게 DB 에 접속하는거 아닌가!?
아니 그렇다면 처음에 말했던 서버-클라이언트 구조에서 클라이언트에서 전송된 데이터가 서버에서 처리하고 이를 반환하는 코드가 별도로 나눠져있을거라고 예상했던 것과 달리 하나의 비즈니스 로직(서버-클라이언트 동일 코드) 에서 처리하고 있음을 알 수 있었다.
그렇다면 어떻게 이게 가능할까?
JMX 덕분에 실제 메일 서버에 접속하여 작업을 처리하기 때문에다.
즉 james-cli.sh 를 통해서 '메일 서버 접속' 을 하고 실제 작업들과 결과들은 클라이언트로 반환함으로써 결과를 전달하는것이 아닌 우리가 원격 서버에 접속해서 작업을 처리하듯이 터미널로 원격 접속을 해서 서버의 명령어를 입력하고, 결과를 직접(터미널의 print) 반환받아 결과를 확인하는것이다.
아 그렇다.
JMX 를 통해서 원격 접속을 하고, 접속한 서버 내에서 데이터 요청을 하기 때문에 모든게 가능했던것이다.