Java tutorial
/* * (C) Copyright 2009 Nuxeo SA (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Contributors: * Laurent Doguin */ package org.nuxeo.cm.core.service.synchronization; import static org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EVENT_CONTEXT_DIRECTORY_NAME; import static org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EVENT_CONTEXT_DIRECTORY_SCHEMA_NAME; import static org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EVENT_CONTEXT_MAILBOX_ENTRY; import static org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EVENT_CONTEXT_MAILBOX_ENTRY_ID; import static org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EVENT_CONTEXT_MAILBOX_OWNER; import static org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EVENT_CONTEXT_MAILBOX_TITLE; import static org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EVENT_CONTEXT_MAILBOX_TYPE; import static org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EVENT_CONTEXT_PARENT_SYNCHRONIZER_ID; import static org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EVENT_CONTEXT_SYNCHRONIZED_DATE; import static org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EVENT_CONTEXT_SYNCHRONIZER_ID; import java.io.Serializable; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.cm.exception.CaseManagementException; import org.nuxeo.cm.exception.CaseManagementRuntimeException; import org.nuxeo.cm.mailbox.Mailbox; import org.nuxeo.cm.mailbox.MailboxConstants; import org.nuxeo.cm.service.CaseManagementDocumentTypeService; import org.nuxeo.cm.service.MailboxTitleGenerator; import org.nuxeo.cm.service.synchronization.MailboxDirectorySynchronizationDescriptor; import org.nuxeo.cm.service.synchronization.MailboxGroupSynchronizationDescriptor; import org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.EventNames; import org.nuxeo.cm.service.synchronization.MailboxSynchronizationConstants.synchronisedState; import org.nuxeo.cm.service.synchronization.MailboxSynchronizationService; import org.nuxeo.cm.service.synchronization.MailboxUserSynchronizationDescriptor; import org.nuxeo.common.utils.ExceptionUtils; import org.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; import org.nuxeo.ecm.core.api.event.CoreEventConstants; import org.nuxeo.ecm.core.api.repository.RepositoryManager; import org.nuxeo.ecm.core.event.EventContext; import org.nuxeo.ecm.core.event.EventProducer; import org.nuxeo.ecm.core.event.ReconnectedEventBundle; import org.nuxeo.ecm.core.event.impl.DocumentEventContext; import org.nuxeo.ecm.core.event.impl.UnboundEventContext; import org.nuxeo.ecm.core.query.sql.model.DateLiteral; import org.nuxeo.ecm.directory.DirectoryException; import org.nuxeo.ecm.platform.usermanager.UserManager; import org.nuxeo.ecm.platform.usermanager.UserManagerImpl; import org.nuxeo.ecm.platform.web.common.exceptionhandling.ExceptionHelper; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.model.ComponentInstance; import org.nuxeo.runtime.model.DefaultComponent; import org.nuxeo.runtime.services.event.Event; import org.nuxeo.runtime.services.event.EventService; import org.nuxeo.runtime.transaction.TransactionHelper; /** * @author Laurent Doguin */ public class MailboxSynchronizationServiceImpl extends DefaultComponent implements MailboxSynchronizationService { private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(MailboxSynchronizationService.class); protected static final String QUERY_GET_MAILBOX_FROM_ID = "SELECT * FROM Mailbox WHERE " + "mlbx:synchronizerId= '%s'"; protected static final String QUERY_GET_MAILBOX_FROM_TITLE = "SELECT * FROM Mailbox WHERE " + "dc:title= '%s'"; protected static final String QUERY_GET_DELETED_MAILBOX = "SELECT * FROM Mailbox WHERE " + "mlbx:origin= '%s' AND mlbx:lastSyncUpdate < TIMESTAMP '%s' AND ecm:currentLifeCycleState != 'deleted'"; protected EventProducer eventProducer; protected final Map<String, MailboxDirectorySynchronizationDescriptor> directorySynchronizer = new HashMap<String, MailboxDirectorySynchronizationDescriptor>(); protected MailboxUserSynchronizationDescriptor userSynchronizer; protected MailboxGroupSynchronizationDescriptor groupSynchronizer; protected int count; protected int total; protected int batchSize = 100; protected boolean deleteOldMailboxes; @Override public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { if (contribution instanceof MailboxDirectorySynchronizationDescriptor) { MailboxDirectorySynchronizationDescriptor synchronizer = (MailboxDirectorySynchronizationDescriptor) contribution; String directoryName = synchronizer.getDirectoryName(); MailboxDirectorySynchronizationDescriptor existingDirSynchronizer = directorySynchronizer .get(directoryName); if (existingDirSynchronizer == null) { if (synchronizer.getDirectoryEntryIdField() != null && synchronizer.getMailboxIdField() != null && synchronizer.getDirectoryName() != null && synchronizer.getTitleGenerator() != null) { directorySynchronizer.put(synchronizer.getDirectoryName(), synchronizer); } else { log.error("Could not register contribution because of missing field(s) in contribution"); } } else { mergeDirectoryContribution(existingDirSynchronizer, synchronizer); } } else if (contribution instanceof MailboxGroupSynchronizationDescriptor) { MailboxGroupSynchronizationDescriptor synchronizer = (MailboxGroupSynchronizationDescriptor) contribution; if (synchronizer.getTitleGenerator() != null) { groupSynchronizer = synchronizer; } else { log.error("Could not register contribution because of missing field(s) in contribution"); } } else if (contribution instanceof MailboxUserSynchronizationDescriptor) { MailboxUserSynchronizationDescriptor synchronizer = (MailboxUserSynchronizationDescriptor) contribution; if (synchronizer.getTitleGenerator() != null) { userSynchronizer = synchronizer; } else { log.error("Could not register contribution because of missing field(s) in contribution"); } } } protected MailboxDirectorySynchronizationDescriptor mergeDirectoryContribution( MailboxDirectorySynchronizationDescriptor existingDirSynchronizer, MailboxDirectorySynchronizationDescriptor synchronizer) { existingDirSynchronizer.setEnabled(synchronizer.isEnabled()); if (synchronizer.getMailboxIdField() != null) { existingDirSynchronizer.setMailboxIdField(synchronizer.getMailboxIdField()); } if (synchronizer.getChildrenField() != null) { existingDirSynchronizer.setChildrenField(synchronizer.getChildrenField()); } if (synchronizer.getDirectoryEntryIdField() != null) { existingDirSynchronizer.setDirectoryEntryIdField(synchronizer.getDirectoryEntryIdField()); } if (synchronizer.getTitleGenerator() != null) { existingDirSynchronizer.setTitleGenerator(synchronizer.getTitleGenerator()); } return existingDirSynchronizer; } @Override public Map<String, MailboxDirectorySynchronizationDescriptor> getSynchronizerMap() { return directorySynchronizer; } @Override public MailboxUserSynchronizationDescriptor getUserSynchronizer() { return userSynchronizer; } @Override public MailboxGroupSynchronizationDescriptor getGroupSynchronizer() { return groupSynchronizer; } @Override public void doSynchronize() { RepositoryManager mgr = Framework.getService(RepositoryManager.class); String batchSize = Framework.getProperty(MailboxConstants.SYNC_BATCH_SIZE_PROPERTY); if (batchSize != null && !"".equals(batchSize)) { this.batchSize = Integer.parseInt(batchSize); } deleteOldMailboxes = Boolean .parseBoolean(Framework.getProperty(MailboxConstants.SYNC_DELETE_MAILBOXES_PROPERTY, "false")); String repositoryName = mgr.getDefaultRepositoryName(); SynchronizeSessionRunner runner = new SynchronizeSessionRunner(repositoryName); runner.runUnrestricted(); flushJaasCache(); } /** * Flushes jaas cache otherwise mailboxes may not be updated on users who login, logout and login again */ // FIXME: should be also called when editing a Mailbox in the interface protected void flushJaasCache() { EventService eventService = Framework.getService(EventService.class); if (eventService != null) { eventService.sendEvent( new Event(UserManagerImpl.USERMANAGER_TOPIC, UserManagerImpl.USERCHANGED_EVENT_ID, this, null)); } } protected void synchronizeGroupList(Map<String, List<String>> groupMap, String directoryName, String directoryIdField, Calendar now, UserManager userManager, MailboxTitleGenerator titleGenerator, CoreSession coreSession, boolean txStarted) throws ClientException { String type = MailboxConstants.type.generic.toString(); String synchronizerId; String generatedTitle; String parentSynchronizerId; DocumentModel groupModel; Set<Entry<String, List<String>>> groupSet = groupMap.entrySet(); Map<String, List<String>> nextChildrenBatch = new HashMap<String, List<String>>(); try { for (Entry<String, List<String>> groupEntry : groupSet) { parentSynchronizerId = groupEntry.getKey(); List<String> groups = groupEntry.getValue(); for (String groupName : groups) { try { groupModel = userManager.getGroupModel(groupName); if (groupModel == null) { log.error("Could not synchronize mailbox for user " + groupName); continue; } synchronizerId = String.format("%s:%s", directoryName, groupName); generatedTitle = titleGenerator.getMailboxTitle(groupModel); synchronizeMailbox(groupModel, directoryName, userManager.getGroupSchemaName(), parentSynchronizerId, synchronizerId, groupName, generatedTitle, null, type, now, coreSession); List<String> groupChilds = userManager.getGroupsInGroup(groupName); if (groupChilds != null && !groupChilds.isEmpty()) { nextChildrenBatch.put(synchronizerId, groupChilds); } if (++count % batchSize == 0) { if (txStarted) { log.debug("Transaction ended during Mailbox synchronization"); TransactionHelper.commitOrRollbackTransaction(); txStarted = TransactionHelper.startTransaction(); log.debug("New Transaction started during Mailbox synchronization"); } } log.debug(String.format("Updated %d/%d group Mailboxes", count, total)); } catch (DirectoryException de) { Throwable t = ExceptionHelper.unwrapException(de); if (t.getMessage().contains("javax.naming.NameNotFoundException")) { log.warn("Searched entry does not exist: " + groupName); } else { throw new CaseManagementRuntimeException("User synchronization failed", de); } } } } if (!nextChildrenBatch.isEmpty()) { synchronizeGroupList(nextChildrenBatch, directoryName, directoryIdField, now, userManager, titleGenerator, coreSession, txStarted); } } catch (Exception e) { // deals with interrupt below ExceptionUtils.checkInterrupt(e); if (txStarted) { TransactionHelper.setTransactionRollbackOnly(); } throw new CaseManagementRuntimeException("Group synchronization failed", e); } } protected void synchronizeUserList(List<String> userIds, String directoryName, String directoryIdField, Calendar now, UserManager userManager, MailboxTitleGenerator titleGenerator, CoreSession coreSession, boolean txStarted) throws ClientException { String type = MailboxConstants.type.personal.toString(); String synchronizerId; String generatedTitle; DocumentModel userModel; int total = userIds.size(); try { for (String userId : userIds) { try { userModel = userManager.getUserModel(userId); if (userModel == null) { log.error("Could not synchronize mailbox for user " + userId); continue; } synchronizerId = String.format("%s:%s", directoryName, userId); generatedTitle = titleGenerator.getMailboxTitle(userModel); synchronizeMailbox(userModel, directoryName, userManager.getUserSchemaName(), "", synchronizerId, userId, generatedTitle, userId, type, now, coreSession); if (++count % batchSize == 0) { if (txStarted) { log.debug("Transaction ended during Mailbox synchronization"); TransactionHelper.commitOrRollbackTransaction(); txStarted = TransactionHelper.startTransaction(); log.debug("New Transaction started during Mailbox synchronization"); } } log.debug(String.format("Updated %d/%d user Mailboxes", count, total)); } catch (DirectoryException de) { Throwable t = ExceptionHelper.unwrapException(de); if (t.getMessage().contains("javax.naming.NameNotFoundException")) { log.warn("Searched entry does not exist: " + userId); } else { throw new CaseManagementRuntimeException("User synchronization failed", de); } } } } catch (Exception e) { // deals with interrupt below ExceptionUtils.checkInterrupt(e); if (txStarted) { TransactionHelper.setTransactionRollbackOnly(); } throw new CaseManagementRuntimeException("User synchronization failed", e); } } protected void synchronizeMailbox(DocumentModel entry, String directoryName, String directorySchema, String parentSynchronizerId, String synchronizerId, String entryId, String generatedTitle, String owner, String type, Calendar now, CoreSession coreSession) throws ClientException { // TODO: hook mailbox resolvers so that synchronizerId can be // customized depending on what mailbox is found DocumentModel cfDoc = getMailboxFromSynchronizerId(synchronizerId, coreSession); Mailbox cf = null; if (cfDoc != null) { cf = cfDoc.getAdapter(Mailbox.class); // use the actual synchronizer id synchronizerId = cf.getSynchronizerId(); } // initiate eventPropertiesMap Map<String, Serializable> eventProperties = new HashMap<String, Serializable>(); eventProperties.put(EVENT_CONTEXT_MAILBOX_ENTRY_ID, entryId); // avoid reconnecting document entry.putContextData(ReconnectedEventBundle.SKIP_REFETCH_DOCUMENT_CONTEXT_KEY, Boolean.TRUE); eventProperties.put(EVENT_CONTEXT_MAILBOX_ENTRY, entry); eventProperties.put(EVENT_CONTEXT_DIRECTORY_NAME, directoryName); eventProperties.put(EVENT_CONTEXT_DIRECTORY_SCHEMA_NAME, directorySchema); eventProperties.put(EVENT_CONTEXT_PARENT_SYNCHRONIZER_ID, parentSynchronizerId); eventProperties.put(EVENT_CONTEXT_SYNCHRONIZER_ID, synchronizerId); eventProperties.put(EVENT_CONTEXT_MAILBOX_TITLE, generatedTitle); eventProperties.put(EVENT_CONTEXT_MAILBOX_OWNER, owner); eventProperties.put(EVENT_CONTEXT_MAILBOX_TYPE, type); eventProperties.put(EVENT_CONTEXT_SYNCHRONIZED_DATE, now); eventProperties.put(CoreEventConstants.SESSION_ID, coreSession.getSessionId()); if (cf != null) { Calendar lastSyncUpdate = cf.getLastSyncUpdate(); // Look if case has already been updated during this batch. if (lastSyncUpdate == null || !lastSyncUpdate.equals(now)) { if (cf.isSynchronized()) { // throw onMailboxUpdated cf.setLastSyncUpdate(now); coreSession.saveDocument(cfDoc); log.debug(String.format("Update Mailbox %s", synchronizerId)); notify(EventNames.onMailboxUpdated.toString(), cf.getDocument(), eventProperties, coreSession); } else { // a user set it as unsynchronized, we don't modify it // anymore cf.setLastSyncUpdate(now); coreSession.saveDocument(cfDoc); log.debug(String.format("set Unsynchronized state for Mailbox %s", synchronizerId)); return; } } } else { cfDoc = getMailboxFromTitle(generatedTitle, coreSession); cf = null; if (cfDoc != null) { cf = cfDoc.getAdapter(Mailbox.class); } if (cf != null) { Calendar lastSyncUpdate = cf.getLastSyncUpdate(); boolean setDoublon = false; // Look if case has already been updated during this batch. if (lastSyncUpdate == null || !lastSyncUpdate.equals(now)) { if (cf.isSynchronized()) { // set synchronizerId, throw onMailboxUpdate cf.setSynchronizerId(synchronizerId); cf.setLastSyncUpdate(now); cf.setOrigin(directoryName); coreSession.saveDocument(cfDoc); log.debug(String.format("Update Mailbox %s", synchronizerId)); notify(EventNames.onMailboxUpdated.toString(), cf.getDocument(), eventProperties, coreSession); } else { setDoublon = true; } } else { setDoublon = true; } if (setDoublon) { // set doublon: A user created a CF with the same title // we assume he doesn't want the same mailbox created cf.setSynchronizeState(synchronisedState.doublon.toString()); cf.setLastSyncUpdate(now); coreSession.saveDocument(cfDoc); log.debug(String.format("set Doublon state for Mailbox %s", synchronizerId)); } } else { // throws onMailboxCreated log.debug(String.format("Creates Mailbox %s", synchronizerId)); DocumentModel mailboxModel = coreSession.createDocumentModel(getMailboxType()); notify(EventNames.onMailboxCreated.toString(), mailboxModel, eventProperties, coreSession); } } } protected String getMailboxType() throws ClientException { CaseManagementDocumentTypeService correspDocumentTypeService = Framework .getService(CaseManagementDocumentTypeService.class); return correspDocumentTypeService.getMailboxType(); } protected void handleDeletedMailboxes(String directoryName, Calendar now, CoreSession coreSession) throws ClientException { String dateLiteral = DateLiteral.dateTimeFormatter.print(now.getTimeInMillis()); String query = String.format(QUERY_GET_DELETED_MAILBOX, directoryName, dateLiteral); DocumentModelList deletedMailboxes = coreSession.query(query); if (deletedMailboxes == null) { return; } Mailbox cf; String synchronizerId; Map<String, Serializable> eventProperties; for (DocumentModel mailboxDoc : deletedMailboxes) { cf = mailboxDoc.getAdapter(Mailbox.class); if (cf == null) { log.error(String.format("Could not get Mailbox adapter for doc %s", mailboxDoc.getId())); continue; } synchronizerId = cf.getSynchronizerId(); eventProperties = new HashMap<String, Serializable>(); eventProperties.put(EVENT_CONTEXT_DIRECTORY_NAME, directoryName); eventProperties.put(EVENT_CONTEXT_SYNCHRONIZER_ID, synchronizerId); eventProperties.put(EVENT_CONTEXT_MAILBOX_TITLE, mailboxDoc.getTitle()); eventProperties.put(EVENT_CONTEXT_SYNCHRONIZED_DATE, now); log.debug(String.format("Mailbox %s has been remove from directory, deleting it.", synchronizerId)); notify(EventNames.onMailboxDeleted.toString(), mailboxDoc, eventProperties, coreSession); } } protected DocumentModel getMailboxFromSynchronizerId(String id, CoreSession coreSession) throws ClientException { String query = String.format(QUERY_GET_MAILBOX_FROM_ID, escape(id)); DocumentModelList mailboxDocs = coreSession.query(query); if (mailboxDocs == null || mailboxDocs.isEmpty()) { log.debug(String.format("Mailbox with id %s does not exist", id)); return null; } else if (mailboxDocs.size() > 1) { // more than one mailbox for given Id // Should not happen log.error(String.format("Found more than one Mailbox for id %s", id)); return null; } DocumentModel mailboxDoc = mailboxDocs.get(0); return mailboxDoc; } protected DocumentModel getMailboxFromTitle(String title, CoreSession coreSession) throws ClientException { String query = String.format(QUERY_GET_MAILBOX_FROM_TITLE, escape(title)); DocumentModelList mailboxDocs = coreSession.query(query); if (mailboxDocs == null || mailboxDocs.isEmpty()) { log.debug(String.format("Mailbox with title %s does not exist", title)); return null; } else if (mailboxDocs.size() > 1) { // more than one mailbox for given title // return first Mailbox log.debug(String.format("Found more than one Mailbox for Title %s, uses first found.", title)); } DocumentModel mailboxDoc = mailboxDocs.get(0); return mailboxDoc; } protected String escape(String s) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '+' || c == '!' || c == '"' || c == '\'') { sb.append('\\'); } sb.append(c); } return sb.toString(); } public void notify(String name, DocumentModel document, Map<String, Serializable> eventProperties, CoreSession session) { EventContext envContext; if (document == null) { envContext = new UnboundEventContext(session, session.getPrincipal(), null); } else { envContext = new DocumentEventContext(session, session.getPrincipal(), document); } envContext.setProperties(eventProperties); getEventProducer().fireEvent(envContext.newEvent(name)); } protected EventProducer getEventProducer() { if (eventProducer == null) { eventProducer = Framework.getService(EventProducer.class); } return eventProducer; } private class SynchronizeSessionRunner extends UnrestrictedSessionRunner { public SynchronizeSessionRunner(String repository) { super(repository); } @Override public void run() throws ClientException { UserManager userManager = Framework.getService(UserManager.class); Calendar now; String directoryName; MailboxTitleGenerator titleGenerator; String directoryIdField; // synchronize group if (groupSynchronizer != null && groupSynchronizer.isEnabled()) { titleGenerator = groupSynchronizer.getTitleGenerator(); if (titleGenerator != null) { now = Calendar.getInstance(); directoryName = userManager.getGroupDirectoryName(); directoryIdField = userManager.getGroupIdField(); List<String> topLevelgroups = userManager.getTopLevelGroups(); Map<String, List<String>> topBatch = new HashMap<String, List<String>>(); topBatch.put("", topLevelgroups); log.info("Start groups synchronization"); count = 0; total = userManager.getGroupIds().size(); boolean txStarted = false; try { if (TransactionHelper.isTransactionActive()) { TransactionHelper.commitOrRollbackTransaction(); log.debug("Commiting existing transaction before Mailbox (group) synchronization"); } txStarted = TransactionHelper.startTransaction(); log.debug("New Transaction started during Mailbox (group) synchronization"); synchronizeGroupList(topBatch, directoryName, directoryIdField, now, userManager, titleGenerator, session, txStarted); } catch (Exception e) { // deals with interrupt below ExceptionUtils.checkInterrupt(e); if (txStarted) { TransactionHelper.setTransactionRollbackOnly(); } throw new CaseManagementRuntimeException("Group synchronization failed", e); } finally { if (txStarted) { TransactionHelper.commitOrRollbackTransaction(); log.debug("Transaction ended during Mailbox synchronization"); } } if (deleteOldMailboxes) { log.info("Looking for deleted group entries"); handleDeletedMailboxes(directoryName, now, session); } log.info("Group directory has been synchronized"); } else { log.error("Could not find GroupTitleGenerator, abort group directory synchronization."); } } // synchronize users if (userSynchronizer != null && userSynchronizer.isEnabled()) { titleGenerator = userSynchronizer.getTitleGenerator(); if (titleGenerator != null) { now = new GregorianCalendar(); directoryName = userManager.getUserDirectoryName(); directoryIdField = userManager.getUserIdField(); List<String> userIds = userManager.getUserIds(); log.debug("Start users synchronization"); count = 0; total = userIds.size(); boolean txStarted = false; try { if (TransactionHelper.isTransactionActive()) { TransactionHelper.commitOrRollbackTransaction(); log.debug("Commiting existing transaction before Mailbox (user) synchronization"); } txStarted = TransactionHelper.startTransaction(); log.debug("New Transaction started during Mailbox (user) synchronization"); synchronizeUserList(userIds, directoryName, directoryIdField, now, userManager, titleGenerator, session, txStarted); } catch (Exception e) { // deals with interrupt below ExceptionUtils.checkInterrupt(e); if (txStarted) { TransactionHelper.setTransactionRollbackOnly(); } throw new CaseManagementRuntimeException("User synchronization failed", e); } finally { if (txStarted) { TransactionHelper.commitOrRollbackTransaction(); log.debug("Transaction ended during Mailbox synchronization"); } } log.debug(String.format("Updated %d/%d mailboxes", count, total)); if (deleteOldMailboxes) { handleDeletedMailboxes(directoryName, now, session); } log.info("User directory has been synchronized"); } else { log.error("Could not find UserTitleGenerator, abort user directory synchronization."); } } } } }