1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 22 * Use is subject to license terms. 23 */ 24 package org.opensolaris.opengrok.management; 25 26 import java.io.File; 27 import java.util.Arrays; 28 import java.util.HashSet; 29 import java.util.Iterator; 30 import java.util.List; 31 import java.util.Set; 32 import java.util.logging.Level; 33 import java.util.logging.Logger; 34 import javax.management.ListenerNotFoundException; 35 import javax.management.MBeanNotificationInfo; 36 import javax.management.MBeanRegistration; 37 import javax.management.MBeanServer; 38 import javax.management.Notification; 39 import javax.management.NotificationEmitter; 40 import javax.management.NotificationFilter; 41 import javax.management.NotificationListener; 42 import javax.management.ObjectName; 43 import org.opensolaris.opengrok.configuration.RuntimeEnvironment; 44 import org.opensolaris.opengrok.history.HistoryGuru; 45 import org.opensolaris.opengrok.index.IndexChangedListener; 46 import org.opensolaris.opengrok.index.Indexer; 47 48 /** 49 * AgentIndexRunner. 50 * @author Jan S Berg 51 */ 52 public final class AgentIndexRunner implements AgentIndexRunnerMBean, NotificationListener, 53 MBeanRegistration, Runnable, IndexChangedListener, NotificationEmitter { 54 55 private transient static AgentIndexRunner indexerInstance = null; 56 private final static String NOTIFICATIONACTIONTYPE = "ogaaction"; 57 private final static String NOTIFICATIONEXCEPTIONTYPE = "ogaexception"; 58 private final static String NOTIFICATIONINFOSTRINGTYPE = "ogainfostring"; 59 private final static String NOTIFICATIONINFOLONGTYPE = "ogainfolong"; 60 private boolean enabled; 61 private transient Thread indexThread = null; 62 private final static Logger log = Logger.getLogger("org.opensolaris.opengrok"); 63 private RuntimeEnvironment env = null; 64 private long lastIndexStart = 0; 65 private long lastIndexFinish = 0; 66 private long lastIndexUsedTime = 0; 67 private Exception lastException = null; 68 private final Set<NotificationHolder> notifListeners = 69 new HashSet<NotificationHolder>(); 70 private static long sequenceNo = 0; 71 private final StringBuilder notifications = new StringBuilder(); 72 private final static int MAXMESSAGELENGTH = 50000; 73 74 /** 75 * The only constructor is private, so other classes will only get an 76 * instance through the static factory method getInstance(). 77 */ 78 private AgentIndexRunner(boolean enabledParam) { 79 enabled = enabledParam; 80 } 81 82 /** 83 * Static factory method to get an instance of AgentIndexRunner. 84 * @param enabledParam if true, the initial instance should be running or not 85 */ 86 @SuppressWarnings("PMD.AvoidSynchronizedAtMethodLevel") 87 public static synchronized AgentIndexRunner getInstance(boolean enabledParam) { 88 if (indexerInstance == null) { 89 indexerInstance = new AgentIndexRunner(enabledParam); 90 } 91 return indexerInstance; 92 } 93 94 public ObjectName preRegister(MBeanServer serverParam, ObjectName name) { 95 return name; 96 } 97 98 public void postRegister(Boolean registrationDone) { 99 // not used 100 } 101 102 public void preDeregister() { 103 // not used 104 } 105 106 public void postDeregister() { 107 // not used 108 } 109 110 public void run() { 111 try { 112 //Indexer ind = new Indexer(); 113 log.info("Running..."); 114 lastIndexStart = System.currentTimeMillis(); 115 lastException = null; 116 doNotify(NOTIFICATIONINFOLONGTYPE, "StartIndexing", Long.valueOf(lastIndexStart)); 117 String configfile = Management.getInstance().getConfigurationFile(); 118 if (configfile == null) { 119 doNotify(NOTIFICATIONEXCEPTIONTYPE, "Missing Configuration file", ""); 120 } 121 File cfgFile = new File(configfile); 122 if (cfgFile.exists()) { 123 env = RuntimeEnvironment.getInstance(); 124 log.info("Running indexer with configuration " + configfile); 125 env.readConfiguration(cfgFile); 126 127 Indexer index = Indexer.getInstance(); 128 int noThreads = Management.getInstance().getNumberOfThreads().intValue(); 129 boolean update = Management.getInstance().getUpdateIndexDatabase().booleanValue(); 130 String[] sublist = Management.getInstance().getSubFiles(); 131 log.info("Update source repositories"); 132 HistoryGuru.getInstance().updateRepositories(); 133 List<String> subFiles = Arrays.asList(sublist); 134 log.info("Starting index, update " + update + " noThreads " + noThreads + " subfiles " + subFiles.size()); 135 index.doIndexerExecution(update, noThreads, subFiles, this); 136 log.info("Finished indexing"); 137 lastIndexFinish = System.currentTimeMillis(); 138 sendNotifications(); 139 doNotify(NOTIFICATIONINFOLONGTYPE, "FinishedIndexing", Long.valueOf(lastIndexFinish)); 140 lastIndexUsedTime = lastIndexFinish - lastIndexStart; 141 String publishhost = Management.getInstance().getPublishServerURL(); 142 if ((publishhost == null) || (publishhost.equals(""))) { 143 log.warning("No publishhost given, not sending updates"); 144 } else { 145 index.sendToConfigHost(env, publishhost); 146 doNotify(NOTIFICATIONINFOSTRINGTYPE, "Published index", publishhost); 147 } 148 149 150 } else { 151 log.warning("Cannot Run indexing without proper configuration file " + configfile); 152 doNotify(NOTIFICATIONEXCEPTIONTYPE, "Configuration file not valid", configfile); 153 } 154 } catch (Exception e) { 155 log.log(Level.SEVERE, 156 "Exception running indexing ", e); 157 lastException = e; 158 } 159 } 160 161 /** 162 * Disables indexer 163 */ 164 public void disable() { 165 enabled = false; 166 } 167 168 /** 169 * Enables the indexer 170 */ 171 public void enable() { 172 enabled = true; 173 } 174 175 /** 176 * Handle timer notifications to the purgatory. 177 * Will start the purger if it is enabled and return immediately. 178 */ 179 public void handleNotification(Notification n, Object hb) { 180 if (n.getType().equals("timer.notification")) { 181 log.finer("Received timer notification"); 182 if (enabled) { 183 index(false); 184 } else { 185 log.info("Indexing is disabled, doing nothing"); 186 } 187 } else { 188 log.warning("Received unknown notification type: " + n.getType()); 189 } 190 } 191 192 /** 193 * The index method starts a thread that will 194 * start indexing part of the opengrok agent. 195 * @param waitForFinished if false the command returns immediately, if true 196 * it will return when the indexing is done. 197 */ 198 public void index(boolean waitForFinished) { 199 log.info("Starting indexing."); 200 /* 201 * Synchronize here to make sure that you never get more than one 202 * indexing thread trying to start at the same time. 203 */ 204 synchronized (this) { 205 if (indexThread != null) { 206 if (indexThread.isAlive()) { 207 log.warning("Previous indexer is still alive, will not start another."); 208 return; 209 } else { 210 log.fine("Previous indexer is no longer alive, starting a new one."); 211 } 212 } 213 indexThread = new Thread(this); 214 try { 215 indexThread.start(); 216 if (!waitForFinished) { 217 return; 218 } 219 log.fine("Waiting for indexer to finish..."); 220 indexThread.join(); 221 log.fine("indexer finished."); 222 } catch (Exception e) { 223 log.log(Level.SEVERE, 224 "Caught Exception while waiting for indexing to finish.", e); 225 } 226 return; 227 } 228 } 229 230 public void fileAdded(String path, String analyzer) { 231 log.info("Added " + path + " analyzer " + analyzer); 232 addFileAction("A:", path); 233 } 234 235 public void fileRemoved(String path) { 236 log.info("File removed " + path); 237 addFileAction("R:", path); 238 } 239 240 public void fileUpdated(String path) { 241 log.info("File updated " + path); 242 addFileAction("U:", path); 243 } 244 245 private void addFileAction(String type, String path) { 246 notifications.append('\n'); 247 notifications.append(type); 248 notifications.append(path); 249 if (notifications.length() > MAXMESSAGELENGTH) { 250 sendNotifications(); 251 } 252 } 253 254 private void sendNotifications() { 255 if (notifications.length() > 0) { 256 doNotify(NOTIFICATIONACTIONTYPE, "FilesInfo", notifications.toString()); 257 notifications.delete(0, notifications.length()); 258 } 259 } 260 261 public long lastIndexTimeFinished() { 262 return lastIndexFinish; 263 } 264 265 public long lastIndexTimeStarted() { 266 return lastIndexStart; 267 } 268 269 public long lastIndexTimeUsed() { 270 return lastIndexUsedTime; 271 } 272 273 public Exception getExceptions() { 274 return lastException; 275 } 276 277 public void addNotificationListener(NotificationListener notiflistener, NotificationFilter notfilt, Object obj) throws IllegalArgumentException { 278 log.info("Adds a notiflistner, with obj " + obj.toString()); 279 if (notiflistener == null) { 280 throw new IllegalArgumentException("Must have legal NotificationListener"); 281 } 282 synchronized (notifListeners) { 283 notifListeners.add(new NotificationHolder(notiflistener, notfilt, obj)); 284 } 285 } 286 287 public void removeNotificationListener(NotificationListener notiflistener) throws ListenerNotFoundException { 288 log.info("removes a notiflistener, no obj"); 289 boolean removed = false; 290 synchronized (notifListeners) { 291 Iterator it = notifListeners.iterator(); 292 while (it.hasNext()) { 293 NotificationHolder mnf = (NotificationHolder) it.next(); 294 if (mnf.getNL().equals(notiflistener)) { 295 it.remove(); 296 removed = true; 297 } 298 } 299 } 300 if (!removed) { 301 throw new ListenerNotFoundException("Didn't remove the given NotificationListener"); 302 } 303 } 304 305 public void removeNotificationListener(NotificationListener notiflistener, NotificationFilter filt, Object obj) throws ListenerNotFoundException { 306 log.info("removes a notiflistener obj " + obj); 307 boolean removed = false; 308 synchronized (notifListeners) { 309 Iterator it = notifListeners.iterator(); 310 while (it.hasNext()) { 311 NotificationHolder mnf = (NotificationHolder) it.next(); 312 if (mnf.getNL().equals(notiflistener) 313 && ((mnf.getFilter() == null) || mnf.getFilter().equals(filt)) 314 && ((mnf.getFilter() == null) || mnf.getObj().equals(obj))) { 315 it.remove(); 316 removed = true; 317 } 318 } 319 } 320 if (!removed) { 321 throw new ListenerNotFoundException("Didn't remove the given NotificationListener"); 322 } 323 } 324 325 /** 326 * Method that the subclass can override, but doesn't have to 327 * @return MBeanNotificationInfo array of notification (and types) this class can emitt. 328 */ 329 public MBeanNotificationInfo[] getNotificationInfo() { 330 MBeanNotificationInfo[] info = new MBeanNotificationInfo[1]; 331 String[] supptypes = {NOTIFICATIONACTIONTYPE, NOTIFICATIONINFOLONGTYPE, NOTIFICATIONINFOSTRINGTYPE}; 332 String name = "AgentIndexRunner"; 333 String descr = "OpenGrok Indexer Notifications"; 334 MBeanNotificationInfo minfo = new MBeanNotificationInfo(supptypes, name, 335 descr); 336 info[0] = minfo; 337 return info; 338 } 339 340 private void doNotify(String type, String msg, Object userdata) { 341 try { 342 log.info("start notifying " + notifListeners.size() + " listeners"); 343 long ts = System.currentTimeMillis(); 344 sequenceNo++; 345 Notification notif = new Notification(type, this, sequenceNo, ts, msg); 346 notif.setUserData(userdata); 347 synchronized (notifListeners) { 348 for (NotificationHolder nl : notifListeners) { 349 log.fine("having one with obj " + nl.getObj()); 350 try { 351 if ((nl.getFilter() == null) || 352 nl.getFilter().isNotificationEnabled(notif)) { 353 nl.getNL().handleNotification(notif, nl.getObj()); 354 } 355 } catch (Exception exnot) { 356 log.log(Level.INFO, "Ex " + exnot, exnot); 357 } 358 } 359 } 360 } catch (Exception ex) { 361 log.log(Level.SEVERE, 362 "Exception during notification sending: " + ex.getMessage(), 363 ex); 364 } 365 } 366 } 367