// // Copyright 2010 Cinch Logic Pty Ltd. // // http://www.chililog.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package org.chililog.server.engine; import java.util.ArrayList; import org.apache.commons.lang.NullArgumentException; import org.chililog.server.common.AppProperties; import org.chililog.server.common.ChiliLogException; import org.chililog.server.common.Log4JLogger; import org.chililog.server.data.RepositoryEntryBO; import org.chililog.server.data.RepositoryEntryController; import org.chililog.server.data.RepositoryConfigBO; import org.chililog.server.data.RepositoryConfigBO.Status; /** * <p> * Runtime information and controller for a repository. * </p> * <p> * From a logical view point, a repository is a "bucket" within ChiliLog where entries from one or more logs are be * stored. * </p> * <p> * From a software view point: * </p> * <ul> * <li>a repository's specification or meta-data is represented by {@link RepositoryConfigBO}.</li> * <li>an individual repository entry (a line in a log file) is represented by {@link RepositoryEntryBO} and is stored * in mongoDB as a record in a collection.</li> * <li>applications communicate with repositories via message queues. Log entries can be deposited in a queue for * processing.</li> * <li>The worker threads, {@link RepositoryStorageWorker}, reads the queued log entries and writes them to mongoDB * using {@link RepositoryEntryController} classes. The exact type of controller is specified as part of the repository * definition in {@link RepositoryConfigBO}.</li> * </ul> * * @author vibul * */ public class Repository { static Log4JLogger _logger = Log4JLogger.getLogger(Repository.class); private RepositoryConfigBO _repoConfig; private ArrayList<RepositoryStorageWorker> _storageWorkers = new ArrayList<RepositoryStorageWorker>(); private Status _status; private boolean _hasStarted = false; /** * Constructor specifying the information needed to create a repository * * @param repoInfo * Repository meta data */ public Repository(RepositoryConfigBO repoInfo) { if (repoInfo == null) { throw new NullArgumentException("repoInfo cannot be null"); } _repoConfig = repoInfo; _status = Status.OFFLINE; return; } /** * Returns the meta data about this repository */ public RepositoryConfigBO getRepoConfig() { return _repoConfig; } /** * Updates the repository information * * @param repoConfig * new repository configuration * @throws ChiliLogException */ public void setRepoConfig(RepositoryConfigBO repoConfig) throws ChiliLogException { if (repoConfig == null) { throw new NullArgumentException("repoConfig cannot be null"); } if (_status != Status.OFFLINE) { throw new ChiliLogException(Strings.REPOSITORY_INFO_UPDATE_ERROR, _repoConfig.getName()); } _repoConfig = repoConfig; } /** * <p> * Starts this repository. Log entries can be produced and consumed. * </p> */ synchronized void bringOnline() throws ChiliLogException { if (_status == Status.ONLINE) { throw new ChiliLogException(Strings.REPOSITORY_ALREADY_ONLINE_ERROR, _repoConfig.getName()); } if (_hasStarted) { // This should not happen when used via the RepositoryService as intended. throw new UnsupportedOperationException( "Restarting a repository is not supported. Instance a new Repository and start it instead."); } try { _logger.info("Bringing Repository '%s' Online", _repoConfig.getName()); MqService mqManager = MqService.getInstance(); AppProperties appProperties = AppProperties.getInstance(); // Setup permissions StringBuilder publisherRoles = new StringBuilder(); publisherRoles.append(_repoConfig.getAdministratorRoleName()).append(","); publisherRoles.append(_repoConfig.getPublisherRoleName()); StringBuilder subscriberRoles = new StringBuilder(); subscriberRoles.append(_repoConfig.getAdministratorRoleName()).append(","); subscriberRoles.append(_repoConfig.getWorkbenchRoleName()).append(","); subscriberRoles.append(_repoConfig.getSubscriberRoleName()); mqManager.addSecuritySettings(_repoConfig.getPubSubAddress(), publisherRoles.toString(), subscriberRoles.toString()); // Update address properties. See // http://hornetq.sourceforge.net/docs/hornetq-2.1.2.Final/user-manual/en/html_single/index.html#queue-attributes.address-settings mqManager.addAddressSettings(_repoConfig.getPubSubAddress(), appProperties.getMqDeadLetterAddress(), null, false, appProperties.getMqRedeliveryMaxAttempts(), _repoConfig.getMaxMemory(), (int) _repoConfig .getPageSize(), (int) _repoConfig.getPageCountCache(), appProperties .getMqRedeliveryDelayMilliseconds(), -1, true, _repoConfig.getMaxMemoryPolicy().toString()); // If we want to store entries, then start queue on the address and workers to consume and write entries if (_repoConfig.getStoreEntriesIndicator()) { // Create queue mqManager.deployQueue(_repoConfig.getPubSubAddress(), _repoConfig.getStorageQueueName(), _repoConfig.getStorageQueueDurableIndicator()); // Start our storage workers startStorageWorkers(); } // Finish _status = Status.ONLINE; _logger.info("Repository '%s' now online.", _repoConfig.getName()); _hasStarted = true; return; } catch (Exception ex) { throw new ChiliLogException(ex, Strings.ONLINE_REPOSITORY_ERROR, _repoConfig.getPubSubAddress(), _repoConfig.getName(), ex.getMessage()); } } /** * Start storage worker threads * * @throws ChiliLogException */ void startStorageWorkers() throws ChiliLogException { // Make sure existing worker threads are stopped stopStorageWorkers(); // Add workers to list try { for (int i = 1; i <= _repoConfig.getStorageQueueWorkerCount(); i++) { String name = String.format("%s StorageWorker #%s", _repoConfig.getName(), i); RepositoryStorageWorker worker = new RepositoryStorageWorker(name, this); worker.start(); _storageWorkers.add(worker); } } catch (Exception ex) { throw new ChiliLogException(ex, Strings.START_REPOSITORY_STORAGE_WORKER_ERROR, _repoConfig.getName(), ex.getMessage()); } } /** * <p> * Makes the repository read only. Log entries can only be searched via the workbench * </p> */ synchronized void makeReadonly() throws ChiliLogException { try { _logger.info("Making Repository '%s' Read Only", _repoConfig.getName()); MqService mqManager = MqService.getInstance(); // Remove permissions so that nobody and stream mqManager.addSecuritySettings(_repoConfig.getPubSubAddress(), null, null); // Stop workers stopStorageWorkers(); // Disconnect remote clients // Finish _status = Status.READONLY; _logger.info("Repository '%s' now Read Only.", _repoConfig.getName()); return; } catch (Exception ex) { throw new ChiliLogException(ex, Strings.READONLY_REPOSITORY_ERROR, _repoConfig.getPubSubAddress(), _repoConfig.getName(), ex.getMessage()); } } /** * <p> * Stops this repository. Log entries cannot be produced or consumed. * </p> */ synchronized void takeOffline() throws ChiliLogException { try { _logger.info("Taking Repository '%s' Offline", _repoConfig.getName()); MqService mqManager = MqService.getInstance(); // Remove permissions so that nobody and stream mqManager.addSecuritySettings(_repoConfig.getPubSubAddress(), null, null); // Stop workers stopStorageWorkers(); // Disconnect remote clients // Finish _status = Status.OFFLINE; _logger.info("Repository '%s' taken Offline.", _repoConfig.getName()); return; } catch (Exception ex) { throw new ChiliLogException(ex, Strings.OFFLINE_REPOSITORY_ERROR, _repoConfig.getPubSubAddress(), _repoConfig.getName(), ex.getMessage()); } } /** * Start writer threads */ void stopStorageWorkers() throws ChiliLogException { try { while (_storageWorkers.size() > 0) { RepositoryStorageWorker worker = _storageWorkers.get(0); worker.stopRunning(); _storageWorkers.remove(0); } } catch (Exception ex) { throw new ChiliLogException(ex, Strings.STOP_REPOSITORY_STORAGE_WORKER_ERROR, _repoConfig.getName(), ex.getMessage()); } } /** * Returns flag to indicate if this repository has started or not */ public synchronized Status getStatus() { return _status; } /** * Returns the array of storage worker threads. This method should only be used for our unit testing! */ ArrayList<RepositoryStorageWorker> getStorageWorkers() { return _storageWorkers; } /** * Make sure we stop */ protected void finalize() throws Throwable { try { takeOffline(); } finally { super.finalize(); } } }