PageRenderTime 61ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/projects/netbeans-7.3/core.startup/src/org/netbeans/core/startup/ModuleList.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 1159 lines | 837 code | 60 blank | 262 comment | 231 complexity | a7f7f03ebe0ee3a84128cfdb7974a8da MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  7. * Other names may be trademarks of their respective owners.
  8. *
  9. * The contents of this file are subject to the terms of either the GNU
  10. * General Public License Version 2 only ("GPL") or the Common
  11. * Development and Distribution License("CDDL") (collectively, the
  12. * "License"). You may not use this file except in compliance with the
  13. * License. You can obtain a copy of the License at
  14. * http://www.netbeans.org/cddl-gplv2.html
  15. * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
  16. * specific language governing permissions and limitations under the
  17. * License. When distributing the software, include this License Header
  18. * Notice in each file and include the License file at
  19. * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
  20. * particular file as subject to the "Classpath" exception as provided
  21. * by Oracle in the GPL Version 2 section of the License file that
  22. * accompanied this code. If applicable, add the following below the
  23. * License Header, with the fields enclosed by brackets [] replaced by
  24. * your own identifying information:
  25. * "Portions Copyrighted [year] [name of copyright owner]"
  26. *
  27. * Contributor(s):
  28. *
  29. * The Original Software is NetBeans. The Initial Developer of the Original
  30. * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
  31. * Microsystems, Inc. All Rights Reserved.
  32. *
  33. * If you wish your version of this file to be governed by only the CDDL
  34. * or only the GPL Version 2, indicate your decision by adding
  35. * "[Contributor] elects to include this software in this distribution
  36. * under the [CDDL or GPL Version 2] license." If you do not indicate a
  37. * single choice of license, a recipient has the option to distribute
  38. * your version of this file under either the CDDL, the GPL Version 2 or
  39. * to extend the choice of license to its licensees as provided above.
  40. * However, if you add GPL Version 2 code and therefore, elected the GPL
  41. * Version 2 license, then the option applies only if the new code is
  42. * made subject to such option by the copyright holder.
  43. */
  44. package org.netbeans.core.startup;
  45. import java.beans.PropertyChangeEvent;
  46. import java.beans.PropertyChangeListener;
  47. import java.io.BufferedInputStream;
  48. import java.io.ByteArrayInputStream;
  49. import java.io.CharArrayWriter;
  50. import java.io.DataOutputStream;
  51. import java.io.File;
  52. import java.io.FileNotFoundException;
  53. import java.io.IOException;
  54. import java.io.InputStream;
  55. import java.io.ObjectInputStream;
  56. import java.io.ObjectOutputStream;
  57. import java.io.OutputStream;
  58. import java.io.OutputStreamWriter;
  59. import java.io.PushbackInputStream;
  60. import java.io.Writer;
  61. import java.util.ArrayList;
  62. import java.util.Collections;
  63. import java.util.HashMap;
  64. import java.util.HashSet;
  65. import java.util.Iterator;
  66. import java.util.List;
  67. import java.util.Map;
  68. import java.util.Map.Entry;
  69. import java.util.Random;
  70. import java.util.Set;
  71. import java.util.TreeMap;
  72. import java.util.jar.JarFile;
  73. import java.util.logging.Level;
  74. import java.util.logging.Logger;
  75. import org.netbeans.DuplicateException;
  76. import org.netbeans.Events;
  77. import org.netbeans.InvalidException;
  78. import org.netbeans.Module;
  79. import org.netbeans.ModuleManager;
  80. import org.netbeans.Stamps;
  81. import org.netbeans.Util;
  82. import org.openide.filesystems.FileAttributeEvent;
  83. import org.openide.filesystems.FileChangeListener;
  84. import org.openide.filesystems.FileEvent;
  85. import org.openide.filesystems.FileLock;
  86. import org.openide.filesystems.FileObject;
  87. import org.openide.filesystems.FileRenameEvent;
  88. import org.openide.filesystems.FileSystem;
  89. import org.openide.filesystems.FileSystem.AtomicAction;
  90. import org.openide.filesystems.FileUtil;
  91. import org.openide.modules.Dependency;
  92. import org.openide.modules.InstalledFileLocator;
  93. import org.openide.modules.SpecificationVersion;
  94. import org.openide.util.Parameters;
  95. import org.openide.util.RequestProcessor;
  96. import org.openide.util.RequestProcessor.Task;
  97. import org.openide.util.Utilities;
  98. import org.openide.util.WeakSet;
  99. import org.openide.xml.EntityCatalog;
  100. import org.openide.xml.XMLUtil;
  101. import org.xml.sax.Attributes;
  102. import org.xml.sax.EntityResolver;
  103. import org.xml.sax.ErrorHandler;
  104. import org.xml.sax.InputSource;
  105. import org.xml.sax.SAXException;
  106. import org.xml.sax.SAXParseException;
  107. import org.xml.sax.XMLReader;
  108. import org.xml.sax.helpers.DefaultHandler;
  109. /** Class responsible for maintaining the list of modules in the IDE persistently.
  110. * This class understands the "module status" XML format, and the list of modules
  111. * present in the Modules/ folder. And it can keep track of module histories.
  112. * Methods must be called from within appropriate mutex access.
  113. * @author Jesse Glick
  114. */
  115. final class ModuleList implements Stamps.Updater {
  116. static final RequestProcessor RP = new RequestProcessor("Module List Updates"); // NOI18N
  117. /** The DTD for a module status. */
  118. public static final String PUBLIC_ID = "-//NetBeans//DTD Module Status 1.0//EN"; // NOI18N
  119. public static final String SYSTEM_ID = "http://www.netbeans.org/dtds/module-status-1_0.dtd"; // NOI18N
  120. /** Whether to validate module XML files.
  121. * Safer; only slows down startup in case quickie parse of XML statuses fails for some reason.
  122. */
  123. private static final boolean VALIDATE_XML = true;
  124. private static final Logger LOG = Logger.getLogger(ModuleList.class.getName());
  125. /** associated module manager */
  126. private final ModuleManager mgr;
  127. /** Modules/ folder containing XML data */
  128. private final FileObject folder;
  129. /** to fire events with */
  130. private final Events ev;
  131. /** map from code name (base)s to statuses of modules on disk */
  132. private final Map<String,DiskStatus> statuses = new HashMap<String,DiskStatus>(100);
  133. /** whether the initial round has been triggered or not */
  134. private boolean triggered = false;
  135. /** listener for changes in modules, etc.; see comment on class Listener */
  136. private final Listener listener = new Listener();
  137. private FileChangeListener weakListener;
  138. /** atomic actions I have used to change Modules/*.xml */
  139. private final Set<FileSystem.AtomicAction> myAtomicActions = Collections.<FileSystem.AtomicAction>synchronizedSet(new WeakSet<FileSystem.AtomicAction>(100));
  140. /** Create the list manager.
  141. * @param mgr the module manager which will actually control the modules at runtime
  142. * @param folder the Modules/ folder on the system file system to scan/write
  143. * @param ev the event logger
  144. */
  145. public ModuleList(ModuleManager mgr, FileObject folder, Events ev) {
  146. this.mgr = mgr;
  147. this.folder = folder;
  148. this.ev = ev;
  149. LOG.fine("ModuleList created, storage in " + folder);
  150. }
  151. /** Read an initial list of modules from disk according to their stored settings.
  152. * Just reads the XML files in the Modules/ directory, and adds those to
  153. * the manager's list of modules. Errors are handled internally.
  154. * Note that the modules encountered are not turned on at this point even if
  155. * the XML says they should be; but they are added to the list of modules to
  156. * enable as needed. All discovered modules are returned.
  157. * Write mutex only.
  158. */
  159. public Set<Module> readInitial() {
  160. ev.log(Events.START_READ);
  161. final Set<Module> read = new HashSet<Module>();
  162. try {
  163. folder.getFileSystem().runAtomicAction(new ReadInitial(read));
  164. } catch (IOException ioe) {
  165. LOG.log(Level.WARNING, null, ioe);
  166. }
  167. return read;
  168. }
  169. final Module createModule(
  170. File jarFile, ModuleHistory hist, boolean reloadable, boolean autoload,
  171. boolean eager, Integer startLevel
  172. ) throws IOException {
  173. Module m;
  174. try {
  175. if (startLevel != null) {
  176. m = mgr.createBundle(jarFile, hist, reloadable, autoload, eager, startLevel);
  177. } else {
  178. m = mgr.create(jarFile, hist, reloadable, autoload, eager);
  179. }
  180. } catch (DuplicateException dupe) {
  181. // XXX should this be tolerated somehow? In case the original is
  182. // in fact scheduled for deletion anyway?
  183. throw new IOException(dupe);
  184. }
  185. return m;
  186. }
  187. /**
  188. * Try to find a module JAR by an XML-supplied name.
  189. * @param jar the JAR name (relative to an install dir, or a full path)
  190. * @param name code name base of the module JAR
  191. * @return an actual JAR file
  192. * @throws FileNotFoundException if no such JAR file could be found on disk
  193. * @throws IOException if something else was wrong
  194. */
  195. private File findJarByName(String jar, String name) throws IOException {
  196. File f = new File(jar);
  197. if (f.isAbsolute()) {
  198. if (!f.isFile()) throw new FileNotFoundException(f.getAbsolutePath());
  199. return f;
  200. } else {
  201. Set<File> jars = InstalledFileLocator.getDefault().locateAll(jar, name, false);
  202. if (jars.isEmpty()) {
  203. throw new FileNotFoundException(jar);
  204. } else if (jars.size() == 1 || Boolean.getBoolean("org.netbeans.core.startup.ModuleList.firstModuleJarWins")) {
  205. return jars.iterator().next();
  206. } else {
  207. // Pick the newest one available.
  208. int major = -1;
  209. SpecificationVersion spec = null;
  210. File newest = null;
  211. for (File candidate : jars) {
  212. int candidateMajor = -1;
  213. SpecificationVersion candidateSpec = null;
  214. JarFile jf = new JarFile(candidate);
  215. try {
  216. java.util.jar.Attributes attr = jf.getManifest().getMainAttributes();
  217. String codename = attr.getValue("OpenIDE-Module");
  218. if (codename != null) {
  219. int slash = codename.lastIndexOf('/');
  220. if (slash != -1) {
  221. candidateMajor = Integer.parseInt(codename.substring(slash + 1));
  222. }
  223. }
  224. String sv = attr.getValue("OpenIDE-Module-Specification-Version");
  225. if (sv != null) {
  226. candidateSpec = new SpecificationVersion(sv);
  227. }
  228. } finally {
  229. jf.close();
  230. }
  231. if (newest == null || candidateMajor > major || (spec != null && candidateSpec != null && candidateSpec.compareTo(spec) > 0)) {
  232. newest = candidate;
  233. major = candidateMajor;
  234. spec = candidateSpec;
  235. }
  236. }
  237. return newest;
  238. }
  239. }
  240. }
  241. /** Actually go ahead and enable modules which were queued up by
  242. * reading methods. Should be done after as many modules
  243. * are collected as possible, in case they have odd mutual
  244. * dependencies. Also begins listening to further changes.
  245. * Pass in a list of boot modules which you would
  246. * like to also try to enable now.
  247. */
  248. public void trigger(Set<Module> boot) {
  249. ev.log(Events.PERF_START, "ModuleList.trigger"); // NOI18N
  250. if (triggered) throw new IllegalStateException("Duplicate call to trigger()"); // NOI18N
  251. Set<Module> maybeEnable = new HashSet<Module>(boot);
  252. for (DiskStatus status: statuses.values()) {
  253. if (status.pendingInstall) {
  254. // We are going to try to turn it on...
  255. status.pendingInstall = false;
  256. Module m = status.module;
  257. if (m.isEnabled() || m.isAutoload() || m.isEager()) throw new IllegalStateException();
  258. maybeEnable.add(m);
  259. }
  260. }
  261. ev.log(Events.PERF_TICK, "modules to enable prepared"); // NOI18N
  262. if (! maybeEnable.isEmpty()) {
  263. ev.log(Events.START_AUTO_RESTORE, maybeEnable);
  264. installNew(maybeEnable);
  265. ev.log(Events.FINISH_AUTO_RESTORE, maybeEnable);
  266. }
  267. LOG.fine("ModuleList.trigger: enabled new modules, flushing changes...");
  268. triggered = true;
  269. flushInitial();
  270. ev.log(Events.PERF_END, "ModuleList.trigger"); // NOI18N
  271. }
  272. // XXX is this method still needed? rethink...
  273. private void installNew(Set<Module> modules) {
  274. if (modules.isEmpty()) {
  275. return;
  276. }
  277. ev.log(Events.PERF_START, "ModuleList.installNew"); // NOI18N
  278. // First suppress all autoloads.
  279. Iterator<Module> it = modules.iterator();
  280. while (it.hasNext()) {
  281. Module m = it.next();
  282. if (m.isAutoload() || m.isEager()) {
  283. it.remove();
  284. } else if (m.isEnabled()) {
  285. // Can happen in obscure circumstances: old module A
  286. // now exists again but with dependency on new module B,
  287. // and a complete build was not done for A+B, so they have
  288. // no existing Modules/ *.xml. In such a case B will already
  289. // have been turned on when restoring A; harmless to remove
  290. // it from the list here.
  291. LOG.fine("#17295 fix active for " + m.getCodeNameBase());
  292. it.remove();
  293. } else if (!m.isValid()) {
  294. // Again can also happen if the user upgrades from one version
  295. // of a module to another. In this case ModuleList correctly removed
  296. // the old dead module from the manager's list, however it is still
  297. // in the set of modules to restore.
  298. LOG.fine("#17471 fix active for " + m.getCodeNameBase());
  299. it.remove();
  300. }
  301. }
  302. List<Module> toEnable = mgr.simulateEnable(modules);
  303. for (Module m: toEnable) {
  304. if (m.isAutoload() || m.isEager()) {
  305. continue;
  306. }
  307. // Quietly turn on others as well:
  308. if (! modules.contains(m)) {
  309. modules.add(m);
  310. }
  311. }
  312. Set<Module> missing = new HashSet<Module>(modules);
  313. missing.removeAll(toEnable);
  314. if (! missing.isEmpty()) {
  315. // Include also problematic autoloads and so on needed by these modules.
  316. Util.transitiveClosureModuleDependencies(mgr, missing);
  317. it = missing.iterator();
  318. while (it.hasNext()) {
  319. Module m = it.next();
  320. if (m.getProblems().isEmpty()) {
  321. it.remove();
  322. }
  323. }
  324. ev.log(Events.FAILED_INSTALL_NEW, missing);
  325. modules.removeAll(missing);
  326. }
  327. try {
  328. mgr.enable(modules);
  329. } catch (InvalidException ie) {
  330. LOG.log(Level.INFO, null, ie);
  331. Module bad = ie.getModule();
  332. if (bad == null) throw new IllegalStateException();
  333. Set<Module> affectedModules = mgr.getModuleInterdependencies(bad, true, true, true);
  334. ev.log(Events.FAILED_INSTALL_NEW_UNEXPECTED, bad, affectedModules, ie);
  335. modules.removeAll (affectedModules);
  336. // Try again without it. Note that some other dependent modules might
  337. // then be in the missing list for the second round.
  338. installNew(modules);
  339. }
  340. ev.log(Events.PERF_END, "ModuleList.installNew"); // NOI18N
  341. }
  342. /** Read an XML file using an XMLReader and parse into a map of properties.
  343. * One distinguished property 'name' is the code name base
  344. * and is taken from the root element. Others are taken
  345. * from the param elements.
  346. * Properties are of type String, Boolean, Integer, or SpecificationVersion
  347. * according to the property name.
  348. * @param is the input stream
  349. * @param reader the XML reader to use to parse; may be null
  350. * @return a map of named properties to values of various types
  351. */
  352. private Map<String,Object> readStatus(InputSource is, XMLReader reader) throws IOException, SAXException {
  353. if (reader == null) {
  354. reader = XMLUtil.createXMLReader(VALIDATE_XML);
  355. reader.setEntityResolver(listener);
  356. reader.setErrorHandler(listener);
  357. }
  358. final Map<String,Object> m = new HashMap<String,Object>();
  359. DefaultHandler handler = new DefaultHandler() {
  360. private String modName;
  361. private String paramName;
  362. private StringBuffer data = new StringBuffer();
  363. public @Override void startElement(String uri,
  364. String localname,
  365. String qname,
  366. Attributes attrs) throws SAXException {
  367. if ("module".equals(qname) ) { // NOI18N
  368. modName = attrs.getValue("name"); // NOI18N
  369. if( modName == null )
  370. throw new SAXException("No module name"); // NOI18N
  371. m.put("name", modName.intern()); // NOI18N
  372. }
  373. else if (modName != null && "param".equals(qname)) { // NOI18N
  374. paramName = attrs.getValue("name");
  375. if( paramName == null ) {
  376. throw new SAXException("No param name"); // NOI18N
  377. }
  378. paramName = paramName.intern();
  379. data.setLength(0);
  380. }
  381. }
  382. public @Override void characters(char[] ch, int start, int len) {
  383. if(modName != null && paramName != null)
  384. data.append( ch, start, len );
  385. }
  386. public @Override void endElement (String uri, String localname, String qname)
  387. throws SAXException
  388. {
  389. if ("param".equals(qname)) { // NOI18N
  390. if (modName != null && paramName != null) {
  391. if (data.length() == 0)
  392. throw new SAXException("No text contents in " + paramName + " of " + modName); // NOI18N
  393. try {
  394. m.put(paramName, processStatusParam(paramName, data.toString()));
  395. } catch (NumberFormatException nfe) {
  396. // From either Integer or SpecificationVersion constructors.
  397. throw (SAXException) new SAXException(nfe.toString()).initCause(nfe);
  398. }
  399. data.setLength(0);
  400. paramName = null;
  401. }
  402. }
  403. else if ("module".equals(qname)) { // NOI18N
  404. modName = null;
  405. }
  406. }
  407. };
  408. reader.setContentHandler(handler);
  409. reader.parse(is);
  410. sanityCheckStatus(m);
  411. return m;
  412. }
  413. /** Parse a param value according to a natural type.
  414. * @param k the param name (must be interned!)
  415. * @param v the raw string value from XML
  416. * @return some parsed value suitable for the status map
  417. */
  418. private Object processStatusParam(String k, String v) throws NumberFormatException {
  419. if (k == "enabled" // NOI18N
  420. || k == "autoload" // NOI18N
  421. || k == "eager" // NOI18N
  422. || k == "reloadable" // NOI18N
  423. ) {
  424. return Boolean.valueOf(v);
  425. } else {
  426. if (k == "startlevel") { // NOI18N
  427. return Integer.valueOf(v);
  428. }
  429. // Other properties are of type String.
  430. // Intern the smaller ones which are likely to be repeated somewhere.
  431. if (v.length() < 100) v = v.intern();
  432. return v;
  433. }
  434. }
  435. /** Just checks that all the right stuff is there.
  436. */
  437. private void sanityCheckStatus(Map<String,Object> m) throws IOException {
  438. String jar = (String) m.get("jar"); // NOI18N
  439. if (jar == null) {
  440. throw new IOException("Must define jar param"); // NOI18N
  441. }
  442. if (Boolean.TRUE.equals(m.get("autoload")) && m.get("enabled") != null) { // NOI18N
  443. throw new IOException("Autoload " + jar + " cannot specify enablement");
  444. }
  445. if (Boolean.TRUE.equals(m.get("eager")) && m.get("enabled") != null) { // NOI18N
  446. throw new IOException("Eager " + jar + " cannot specify enablement");
  447. }
  448. }
  449. // Encoding irrelevant for these getBytes() calls: all are ASCII...
  450. // (unless someone has their system encoding set to UCS-16!)
  451. private static final byte[] MODULE_XML_INTRO = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE module PUBLIC \"-//NetBeans//DTD Module Status 1.0//EN\"\n \"http://www.netbeans.org/dtds/module-status-1_0.dtd\">\n<module name=\"".getBytes(); // NOI18N
  452. // private static final byte[] MODULE_XML_DIV1 = ">\n <param name=\"".getBytes(); // NOI18N
  453. private static final byte[] MODULE_XML_INTRO_END = ">\n".getBytes(); // NOI18N
  454. private static final byte[] MODULE_XML_DIV2 = " <param name=\"".getBytes(); // NOI18N
  455. private static final byte[] MODULE_XML_DIV3 = "/param>\n".getBytes(); // NOI18N
  456. private static final byte[] MODULE_XML_END = "/module>\n".getBytes(); // NOI18N
  457. /** Just like {@link #readStatus(InputSource,XMLReader)} but avoids using an XML parser.
  458. * If it does not manage to parse it this way, it returns null, in which case
  459. * you have to use a real parser.
  460. * @see "#26786"
  461. */
  462. private Map<String, Object> readStatus(InputStream is, boolean checkEOF) throws IOException {
  463. PushbackInputStream pbis = new PushbackInputStream(is, 1);
  464. Map<String,Object> m = new HashMap<String,Object>(15);
  465. if (!expect(pbis, MODULE_XML_INTRO)) {
  466. LOG.fine("Could not read intro");
  467. return null;
  468. }
  469. String name = readTo(pbis, '"');
  470. if (name == null) {
  471. LOG.fine("Could not read code name base");
  472. return null;
  473. }
  474. m.put("name", name.intern()); // NOI18N
  475. if (!expect(pbis, MODULE_XML_INTRO_END)) {
  476. LOG.fine("Could not read stuff after cnb");
  477. return null;
  478. }
  479. // Now we have <param>s some number of times, finally </module>.
  480. PARSE:
  481. while (true) {
  482. int c = pbis.read();
  483. switch (c) {
  484. case ' ':
  485. // <param>
  486. if (!expect(pbis, MODULE_XML_DIV2)) {
  487. LOG.fine("Could not read up to param");
  488. return null;
  489. }
  490. String k = readTo(pbis, '"');
  491. if (k == null) {
  492. LOG.fine("Could not read param");
  493. return null;
  494. }
  495. k = k.intern();
  496. if (pbis.read() != '>') {
  497. LOG.fine("No > at end of <param> " + k);
  498. return null;
  499. }
  500. String v = readTo(pbis, '<');
  501. if (v == null) {
  502. LOG.fine("Could not read value of " + k);
  503. return null;
  504. }
  505. if (!expect(pbis, MODULE_XML_DIV3)) {
  506. LOG.fine("Could not read end of param " + k);
  507. return null;
  508. }
  509. try {
  510. m.put(k, processStatusParam(k, v));
  511. } catch (NumberFormatException nfe) {
  512. LOG.fine("Number misparse: " + nfe);
  513. return null;
  514. }
  515. break;
  516. case '<':
  517. // </module>
  518. if (!expect(pbis, MODULE_XML_END)) {
  519. LOG.fine("Strange ending");
  520. return null;
  521. }
  522. if (!checkEOF) {
  523. break PARSE;
  524. }
  525. if (pbis.read() != -1) {
  526. LOG.fine("Trailing garbage");
  527. return null;
  528. }
  529. // Success!
  530. break PARSE;
  531. default:
  532. LOG.fine("Strange stuff after <param>s: " + c);
  533. return null;
  534. }
  535. }
  536. sanityCheckStatus(m);
  537. return m;
  538. }
  539. /** Read some stuff from a stream and skip over it.
  540. * Newline conventions are normalized to Unix \n.
  541. * @return true upon success, false if stream contained something else
  542. */
  543. private boolean expect(PushbackInputStream is, byte[] stuff) throws IOException {
  544. int len = stuff.length;
  545. boolean inNewline = false;
  546. for (int i = 0; i < len; ) {
  547. int c = is.read();
  548. if (c == 10 || c == 13) {
  549. // Normalize: s/[\r\n]+/\n/g
  550. if (inNewline) {
  551. continue;
  552. } else {
  553. inNewline = true;
  554. c = 10;
  555. }
  556. } else {
  557. inNewline = false;
  558. }
  559. if (c != stuff[i++]) {
  560. return false;
  561. }
  562. }
  563. if (stuff[len - 1] == 10) {
  564. // Expecting something ending in a \n - so we have to
  565. // read any further \r or \n and discard.
  566. int c = is.read();
  567. if (c != -1 && c != 10 && c != 13) {
  568. // Got some non-newline character, push it back!
  569. is.unread(c);
  570. }
  571. }
  572. return true;
  573. }
  574. /** Read a maximal string until delim is encountered (which will be removed from stream).
  575. * This impl reads only ASCII, for speed.
  576. * Newline conventions are normalized to Unix \n.
  577. * @return the read string, or null if the delim is not encountered before EOF.
  578. */
  579. private String readTo(InputStream is, char delim) throws IOException {
  580. if (delim == 10) {
  581. // Not implemented - stream might have "foo\r\n" and we would
  582. // return "foo" and leave "\n" in the stream.
  583. throw new IOException("Not implemented"); // NOI18N
  584. }
  585. CharArrayWriter caw = new CharArrayWriter(100);
  586. boolean inNewline = false;
  587. while (true) {
  588. int c = is.read();
  589. if (c == -1) return null;
  590. if (c > 126) return null;
  591. if (c == 10 || c == 13) {
  592. // Normalize: s/[\r\n]+/\n/g
  593. if (inNewline) {
  594. continue;
  595. } else {
  596. inNewline = true;
  597. c = 10;
  598. }
  599. } else if (c < 32 && c != 9) {
  600. // Random control character!
  601. return null;
  602. } else {
  603. inNewline = false;
  604. }
  605. if (c == delim) {
  606. return caw.toString();
  607. } else {
  608. caw.write(c);
  609. }
  610. }
  611. }
  612. final Map<String,Map<String,Object>> readCache() {
  613. InputStream is = Stamps.getModulesJARs().asStream("all-modules.dat"); // NOI18N
  614. if (is == null) {
  615. // schedule write for later
  616. writeCache();
  617. return null;
  618. }
  619. LOG.log(Level.FINEST, "Reading cache all-modules.dat");
  620. try {
  621. ObjectInputStream ois = new ObjectInputStream(is);
  622. Map<String,Map<String,Object>> ret = new HashMap<String, Map<String, Object>>(1333);
  623. while (is.available() > 0) {
  624. Map<String, Object> prop = readStatus(ois, false);
  625. if (prop == null) {
  626. LOG.log(Level.CONFIG, "Cache is invalid all-modules.dat");
  627. return null;
  628. }
  629. Set<?> deps;
  630. try {
  631. deps = (Set<?>) ois.readObject();
  632. } catch (ClassNotFoundException ex) {
  633. throw new IOException(ex);
  634. }
  635. prop.put("deps", deps);
  636. String cnb = (String)prop.get("name"); // NOI18N
  637. ret.put(cnb, prop);
  638. }
  639. is.close();
  640. return ret;
  641. } catch (IOException ex) {
  642. LOG.log(Level.INFO, "Cannot read cache", ex);
  643. writeCache();
  644. return null;
  645. }
  646. }
  647. final void writeCache() {
  648. Stamps.getModulesJARs().scheduleSave(this, "all-modules.dat", false);
  649. }
  650. @Override
  651. public void cacheReady() {
  652. }
  653. @Override
  654. public void flushCaches(DataOutputStream os) throws IOException {
  655. ObjectOutputStream oss = new ObjectOutputStream(os);
  656. for (Module m : mgr.getModules()) {
  657. if (m.isFixed()) {
  658. continue;
  659. }
  660. Map<String, Object> prop = computeProperties(m);
  661. writeStatus(prop, oss);
  662. oss.writeObject(m.getDependencies());
  663. }
  664. }
  665. /** Write a module's status to disk in the form of an XML file.
  666. * The map of parameters must contain one named 'name' with the code
  667. * name base of the module.
  668. */
  669. private void writeStatus(Map<String, Object> m, OutputStream os) throws IOException {
  670. String codeName = (String)m.get("name"); // NOI18N
  671. if (codeName == null)
  672. throw new IllegalArgumentException("no code name present"); // NOI18N
  673. Writer w = new OutputStreamWriter(os, "UTF-8"); // NOI18N
  674. w.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); // NOI18N
  675. w.write("<!DOCTYPE module PUBLIC \""); // NOI18N
  676. w.write(PUBLIC_ID);
  677. w.write("\"\n \""); // NOI18N
  678. w.write(SYSTEM_ID);
  679. w.write("\">\n"); // NOI18N
  680. w.write("<module name=\""); // NOI18N
  681. w.write(XMLUtil.toAttributeValue(codeName)); // NOI18N
  682. w.write("\">\n"); // NOI18N
  683. // Use TreeMap to sort the keys by name; since the module status files might
  684. // be version-controlled we want to avoid gratuitous format changes.
  685. for (Map.Entry<String, Object> entry: new TreeMap<String, Object>(m).entrySet()) {
  686. String name = entry.getKey();
  687. if (
  688. name.equals("name") || // NOI18N
  689. name.equals("deps") // NOI18N
  690. ) {
  691. // Skip this one, it is a pseudo-param.
  692. continue;
  693. }
  694. Object val = entry.getValue();
  695. w.write(" <param name=\""); // NOI18N
  696. w.write(XMLUtil.toAttributeValue(name)); // NOI18N
  697. w.write("\">"); // NOI18N
  698. w.write(XMLUtil.toElementContent(val.toString()));
  699. w.write("</param>\n"); // NOI18N
  700. }
  701. w.write("</module>\n"); // NOI18N
  702. w.flush();
  703. }
  704. /** Write information about a module out to disk.
  705. * If the old status is given as null, this is a newly
  706. * added module; create an appropriate status and return it.
  707. * Else update the existing status and return it (it is
  708. * assumed properties are already updated).
  709. * Should write the XML and create/rewrite/delete the serialized
  710. * installer file as needed.
  711. */
  712. private DiskStatus writeOut(Module m, DiskStatus old) throws IOException {
  713. final DiskStatus nue;
  714. if (old == null) {
  715. nue = new DiskStatus();
  716. nue.module = m;
  717. nue.setDiskProps(computeProperties(m));
  718. } else {
  719. nue = old;
  720. }
  721. FileSystem.AtomicAction aa = new FileSystem.AtomicAction() {
  722. public void run() throws IOException {
  723. if (nue.file == null) {
  724. nue.file = FileUtil.createData(folder, ((String)nue.diskProps.get("name")).replace('.', '-') + ".xml"); // NOI18N
  725. } else {
  726. // Just verify that no one else touched it since we last did.
  727. if (/*nue.lastApprovedChange != nue.file.lastModified().getTime()*/nue.dirty) {
  728. // Oops, something is wrong. #156764 - log at lower level.
  729. LOG.log(Level.INFO, null, new IOException("Will not clobber external changes in " + nue.file));
  730. return;
  731. }
  732. }
  733. LOG.fine("ModuleList: (re)writing " + nue.file);
  734. FileLock lock = nue.file.lock();
  735. try {
  736. OutputStream os = nue.file.getOutputStream(lock);
  737. try {
  738. writeStatus(nue.diskProps, os);
  739. } finally {
  740. os.close();
  741. }
  742. } finally {
  743. lock.releaseLock();
  744. }
  745. //nue.lastApprovedChange = nue.file.lastModified().getTime();
  746. }
  747. };
  748. myAtomicActions.add(aa);
  749. folder.getFileSystem().runAtomicAction(aa);
  750. return nue;
  751. }
  752. /** Delete a module from disk.
  753. */
  754. private void deleteFromDisk(final Module m, final DiskStatus status) throws IOException {
  755. final String nameDashes = m.getCodeNameBase().replace('.', '-'); // NOI18N
  756. //final long expectedTime = status.lastApprovedChange;
  757. FileSystem.AtomicAction aa = new FileSystem.AtomicAction() {
  758. public void run() throws IOException {
  759. FileObject xml = folder.getFileObject(nameDashes, "xml"); // NOI18N
  760. if (xml == null) {
  761. // Could be that the XML was already deleted externally, etc.
  762. LOG.fine("ModuleList: " + m + "'s XML already gone from disk");
  763. return;
  764. }
  765. //if (xml == null) throw new IOException("No such XML file: " + nameDashes + ".xml"); // NOI18N
  766. if (status.dirty) {
  767. // Someone wrote to the file since we did. Don't delete it blindly!
  768. // XXX should this throw an exception, or just warn??
  769. throw new IOException("Unapproved external change to " + xml); // NOI18N
  770. }
  771. LOG.fine("ModuleList: deleting " + xml);
  772. /*
  773. if (xml.lastModified().getTime() != expectedTime) {
  774. // Someone wrote to the file since we did. Don't delete it blindly!
  775. throw new IOException("Unapproved external change to " + xml); // NOI18N
  776. }
  777. */
  778. xml.delete();
  779. FileObject ser = folder.getFileObject(nameDashes, "ser"); // NOI18N
  780. if (ser != null) {
  781. LOG.fine("(and also " + ser + ")");
  782. ser.delete();
  783. }
  784. }
  785. };
  786. myAtomicActions.add(aa);
  787. folder.getFileSystem().runAtomicAction(aa);
  788. }
  789. /** Flush the initial state of the module installer after startup to disk.
  790. * This means:
  791. * 1. Find all modules in the manager.
  792. * 2. Anything for which we have no status, write out its XML now
  793. * and create a status object for it.
  794. * 3. Anything for which we have a status, compare the status we
  795. * have to its current state (don't forget the installer
  796. * serialization state--if this is nonnull, that counts as an
  797. * automatic change because it means the module was loaded and
  798. * needed to store something).
  799. * 4. For any changes found in 3., write out new XML (and if
  800. * there is any installer state, a new installer ser).
  801. * 5. Attach listeners to the manager and all modules to catch further
  802. * changes in the system so they may be flushed.
  803. * We could in principle start listening right after readInitial()
  804. * but it should be more efficient to wait and see what has really
  805. * changed. Also, some XML may say that a module is enabled, and in
  806. * fact trigger() was not able to turn it on. In that case, this will
  807. * show up as a change in step 3. and we will rewrite it as disabled.
  808. * Called within write mutex by trigger().
  809. */
  810. private void flushInitial() {
  811. LOG.fine("Flushing initial module list...");
  812. // Find all modules for which we have status already. Treat
  813. // them as possibly changed, and attach listeners.
  814. for (Module m : mgr.getModules()) {
  815. DiskStatus status = statuses.get(m.getCodeNameBase());
  816. if (status != null) {
  817. moduleChanged(m, status);
  818. m.addPropertyChangeListener(listener);
  819. }
  820. }
  821. // Now find all new and deleted modules.
  822. moduleListChanged();
  823. // And listener for new or deleted modules.
  824. mgr.addPropertyChangeListener(listener);
  825. }
  826. /** Does the real work when the list of modules changes.
  827. * Finds newly added modules, creates XML and status for
  828. * them and begins listening for changes; finds deleted
  829. * modules, removes their listener, XML, and status.
  830. * May be called within read or write mutex; since it
  831. * could be in the read mutex, synchronize (on statuses).
  832. */
  833. final void moduleListChanged() {
  834. synchronized (statuses) {
  835. if (LOG.isLoggable(Level.FINE)) {
  836. LOG.fine("ModuleList: moduleListChanged; statuses=" + statuses);
  837. }
  838. // Newly added modules first.
  839. for (Module m : mgr.getModules()) {
  840. if (m.isFixed() || m.getJarFile() == null) {
  841. // No way, we don't manage these.
  842. continue;
  843. }
  844. final String name = m.getCodeNameBase();
  845. if (statuses.get(name) == null) {
  846. // Yup, it's new. Write it out.
  847. LOG.fine("moduleListChanged: added: " + m);
  848. try {
  849. statuses.put(name, writeOut(m, null));
  850. m.addPropertyChangeListener(listener);
  851. } catch (IOException ioe) {
  852. LOG.log(Level.WARNING, null, ioe);
  853. // XXX Now what? Keep it in our list or what??
  854. }
  855. }
  856. }
  857. // Now deleted & recreated modules.
  858. Iterator<DiskStatus> it = statuses.values().iterator();
  859. while (it.hasNext()) {
  860. DiskStatus status = it.next();
  861. if (! status.module.isValid()) {
  862. status.module.removePropertyChangeListener(listener);
  863. Module nue = mgr.get(status.module.getCodeNameBase());
  864. if (nue != null) {
  865. // Deleted, but a new module with the same code name base
  866. // was created (#5922 e.g.). So change the module reference
  867. // in the status and write out any changes to disk.
  868. LOG.fine("moduleListChanged: recreated: " + nue);
  869. nue.addPropertyChangeListener(listener);
  870. status.module = nue;
  871. moduleChanged(nue, status);
  872. } else {
  873. // Newly deleted.
  874. LOG.fine("moduleListChanged: deleted: " + status.module);
  875. it.remove();
  876. try {
  877. deleteFromDisk(status.module, status);
  878. } catch (IOException ioe) {
  879. LOG.log(Level.WARNING, null, ioe);
  880. }
  881. }
  882. }
  883. }
  884. }
  885. }
  886. /** Does the real work when one module changes.
  887. * Compares old and new state and writes XML
  888. * (and perhaps serialized installer state) as needed.
  889. * May be called within read or write mutex; since it
  890. * could be in the read mutex, synchronize (on status).
  891. */
  892. private void moduleChanged(Module m, DiskStatus status) {
  893. synchronized (status) {
  894. LOG.log(Level.FINE, "moduleChanged: {0}", m);
  895. Map<String,Object> newProps = computeProperties(m);
  896. int cnt = 0;
  897. for (Map.Entry<String, Object> entry : status.diskProps.entrySet()) {
  898. if (entry.getKey().equals("deps")) { // NOI18N
  899. continue;
  900. }
  901. Object snd = newProps.get(entry.getKey());
  902. if (!entry.getValue().equals(snd)) {
  903. cnt = -1;
  904. break;
  905. }
  906. cnt++;
  907. }
  908. if (cnt != newProps.size()) {
  909. if (LOG.isLoggable(Level.FINE)) {
  910. Set<Map.Entry<String,Object>> changes = new HashSet<Map.Entry<String,Object>>(newProps.entrySet());
  911. changes.removeAll(status.diskProps.entrySet());
  912. LOG.fine("ModuleList: changes are " + changes);
  913. }
  914. // We need to write changes.
  915. status.setDiskProps(newProps);
  916. try {
  917. writeOut(m, status);
  918. } catch (IOException ioe) {
  919. LOG.log(Level.WARNING, null, ioe);
  920. // XXX now what? continue to manage it anyway?
  921. }
  922. writeCache();
  923. }
  924. }
  925. }
  926. /** Compute what properties we would want to store in XML
  927. * for this module. I.e. 'name', 'reloadable', etc.
  928. */
  929. private Map<String,Object> computeProperties(Module m) {
  930. if (m.isFixed() || ! m.isValid()) throw new IllegalArgumentException("fixed or invalid: " + m); // NOI18N
  931. Map<String,Object> p = new HashMap<String,Object>();
  932. p.put("name", m.getCodeNameBase()); // NOI18N
  933. if (!m.isAutoload() && !m.isEager()) {
  934. p.put("enabled", m.isEnabled()); // NOI18N
  935. }
  936. p.put("autoload", m.isAutoload()); // NOI18N
  937. p.put("eager", m.isEager()); // NOI18N
  938. p.put("reloadable", m.isReloadable()); // NOI18N
  939. if (m.getStartLevel() > 0) {
  940. p.put("startlevel", m.getStartLevel()); // NOI18N
  941. }
  942. if (m.getHistory() instanceof ModuleHistory) {
  943. ModuleHistory hist = (ModuleHistory) m.getHistory();
  944. p.put("jar", hist.getJar()); // NOI18N
  945. }
  946. return p;
  947. }
  948. final void init() {
  949. weakListener = FileUtil.weakFileChangeListener(listener, folder);
  950. folder.getChildren();
  951. folder.addFileChangeListener(weakListener);
  952. }
  953. final void shutDown() {
  954. folder.removeFileChangeListener(weakListener);
  955. }
  956. /** Listener for changes in set of modules and various properties of individual modules.
  957. * Also serves as a strict error handler for XML parsing.
  958. * Also listens to changes in the Modules/ folder and processes them in req proc.
  959. */
  960. private final class Listener implements PropertyChangeListener, ErrorHandler, EntityResolver, FileChangeListener, Runnable {
  961. private final RequestProcessor.Task task;
  962. Listener() {
  963. task = RP.create(this);
  964. }
  965. // Property change coming from ModuleManager or some known Module.
  966. private boolean listening = true;
  967. public void propertyChange(PropertyChangeEvent evt) {
  968. if (! triggered) throw new IllegalStateException("Property change before trigger()"); // NOI18N
  969. // REMEMBER this is inside *read* mutex, it is forbidden to even attempt
  970. // to get write access synchronously here!
  971. String prop = evt.getPropertyName();
  972. Object src = evt.getSource();
  973. if (!listening) {
  974. // #27106: do not react to our own changes while we are making them
  975. if (LOG.isLoggable(Level.FINE)) {
  976. LOG.fine("ModuleList: ignoring own change " + prop + " from " + src);
  977. }
  978. return;
  979. }
  980. if (ModuleManager.PROP_CLASS_LOADER.equals(prop) ||
  981. ModuleManager.PROP_ENABLED_MODULES.equals(prop) ||
  982. Module.PROP_CLASS_LOADER.equals(prop) ||
  983. Module.PROP_PROBLEMS.equals(prop) ||
  984. Module.PROP_VALID.equals(prop)) {
  985. // Properties we are not directly interested in, ignore.
  986. // Note that rather than paying attention to PROP_VALID
  987. // we simply deal with deletions when PROP_MODULES is fired.
  988. return;
  989. } else if (ModuleManager.PROP_MODULES.equals(prop)) {
  990. moduleListChanged();
  991. } else if (src instanceof Module) {
  992. // enabled, manifest, reloadable, possibly other stuff in the future
  993. Module m = (Module)src;
  994. if (! m.isValid()) {
  995. // Skip it. We will get PROP_MODULES sometime anyway.
  996. return;
  997. }
  998. DiskStatus status = statuses.get(m.getCodeNameBase());
  999. if (status == null) {
  1000. throw new IllegalStateException("Unknown module " + m + "; statuses=" + statuses); // NOI18N
  1001. }
  1002. if (status.pendingInstall && Module.PROP_ENABLED.equals(prop)) {
  1003. throw new IllegalStateException("Got PROP_ENABLED on " + m + " before trigger()"); // NOI18N
  1004. }
  1005. moduleChanged(m, status);
  1006. } else {
  1007. LOG.fine("Unexpected property change: " + evt + " prop=" + prop + " src=" + src);
  1008. }
  1009. }
  1010. // SAX stuff.
  1011. public void warning(SAXParseException e) throws SAXException {
  1012. LOG.log(Level.WARNING, null, e);
  1013. }
  1014. public void error(SAXParseException e) throws SAXException {
  1015. throw e;
  1016. }
  1017. public void fatalError(SAXParseException e) throws SAXException {
  1018. throw e;
  1019. }
  1020. public InputSource resolveEntity(String pubid, String sysid) throws SAXException, IOException {
  1021. if (pubid.equals(PUBLIC_ID)) {
  1022. if (VALIDATE_XML) {
  1023. // We certainly know where to get this from.
  1024. return new InputSource(ModuleList.class.getResource("module-status-1_0.dtd").toExternalForm()); // NOI18N
  1025. } else {
  1026. // Not validating, don't load any DTD! Significantly faster.
  1027. return new InputSource(new ByteArrayInputStream(new byte[0]));
  1028. }
  1029. } else {
  1030. // Otherwise try the standard places.
  1031. return EntityCatalog.getDefault().resolveEntity(pubid, sysid);
  1032. }
  1033. }
  1034. // Changes in Modules/ folder.
  1035. public void fileDeleted(FileEvent ev) {
  1036. if (isOurs(ev)) {
  1037. if (LOG.isLoggable(Level.FINE)) {
  1038. LOG.fine("ModuleList: got expected deletion " + ev);
  1039. }
  1040. return;
  1041. }
  1042. FileObject fo = ev.getFile();
  1043. fileDeleted0(fo.getName(), fo.getExt()/*, ev.getTime()*/);
  1044. }
  1045. public void fileDataCreated(FileEvent ev) {
  1046. if (isOurs(ev)) {
  1047. if (LOG.isLoggable(Level.FINE)) {
  1048. LOG.fine("ModuleList: got expected creation " + ev);
  1049. }
  1050. return;
  1051. }
  1052. FileObject fo = ev.getFile();
  1053. fileCreated0(fo.getName(), fo.getExt()/*, ev.getTime()*/);
  1054. }
  1055. public void fileRenamed(FileRenameEvent ev) {
  1056. if (isOurs(ev)) {
  1057. throw new IllegalStateException("I don't rename anything! " + ev); // NOI18N
  1058. }
  1059. FileObject fo = ev.getFile();
  1060. fileDeleted0(ev.getName(), ev.getExt()/*, ev.getTime()*/);
  1061. fileCreated0(fo.getName(), fo.getExt()/*, ev.getTime()*/);
  1062. }
  1063. private void fileCreated0(String name, String ext/*, long time*/) {
  1064. if ("xml".equals(ext)) { // NOI18N
  1065. String codenamebase = name.replace('-', '.');
  1066. DiskStatus status = statuses.get(codenamebase);
  1067. LOG.fine("ModuleList: outside file creation event for " + codenamebase);
  1068. if (status != null) {
  1069. // XXX should this really happen??
  1070. status.dirty = true;
  1071. }
  1072. runme();
  1073. } else if ("ser".equals(ext)) { // NOI18N
  1074. // XXX handle newly added installers?? or not
  1075. } // else ignore
  1076. }
  1077. private void fileDeleted0(String name, String ext/*, long time*/) {
  1078. if ("xml".equals(ext)) { // NOI18N
  1079. // Removed module.
  1080. String codenamebase = name.replace('-', '.');
  1081. DiskStatus status = statuses.get(codenamebase);
  1082. LOG.fine("ModuleList: outside file deletion event for " + codenamebase);
  1083. if (status != null) {
  1084. // XXX should this ever happen?
  1085. status.dirty = true;
  1086. }
  1087. runme();
  1088. } else if ("ser".equals(ext)) { // NOI18N
  1089. // XXX handle newly deleted installers?? or not
  1090. } // else ignore
  1091. }
  1092. public void fileChanged(FileEvent ev) {
  1093. if (isOurs(ev)) {
  1094. if (LOG.isLoggable(Level.FINE)) {
  1095. LOG.fine("ModuleList: got expected modification " + ev);
  1096. }
  1097. return;
  1098. }
  1099. FileObject fo = ev.getFile();