| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.netbeans.nbbuild; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedReader; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| import java.util.regex.Pattern; |
| import java.util.zip.CRC32; |
| import java.util.zip.ZipEntry; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| import javax.xml.xpath.XPathFactory; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.taskdefs.Get; |
| import org.apache.tools.ant.types.FileSet; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.netbeans.nbbuild.AutoUpdateCatalogParser.ModuleItem; |
| import org.netbeans.nbbuild.extlibs.DownloadBinaries; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * |
| * @author Jaroslav Tulach <jtulach@netbeans.org> |
| */ |
| public class AutoUpdate extends Task { |
| private List<Modules> modules = new ArrayList<>(); |
| private FileSet nbmSet; |
| private File download; |
| private File dir; |
| private File cluster; |
| private URL catalog; |
| private boolean force; |
| |
| public void setUpdateCenter(URL u) { |
| catalog = u; |
| } |
| |
| public FileSet createNbms() { |
| if (nbmSet != null) { |
| throw new BuildException("Just one nbms set allowed"); |
| } |
| nbmSet = new FileSet(); |
| return nbmSet; |
| } |
| |
| public void setInstallDir(File dir) { |
| this.dir = dir; |
| } |
| |
| public void setToDir(File dir) { |
| this.cluster = dir; |
| } |
| |
| public void setDownloadDir(File dir) { |
| this.download = dir; |
| } |
| |
| /** Forces rewrite even the version of a module is not newer */ |
| public void setForce(boolean force) { |
| this.force = force; |
| } |
| |
| public Modules createModules() { |
| final Modules m = new Modules(); |
| modules.add(m); |
| return m; |
| } |
| |
| @Override |
| public void execute() throws BuildException { |
| boolean downloadOnly = false; |
| if ((dir != null) == (cluster != null)) { |
| if (dir == null && cluster == null && download != null) { |
| log("Going to download NBMs only to " + download); |
| downloadOnly = true; |
| } else { |
| throw new BuildException("Specify either todir or installdir"); |
| } |
| } |
| Map<String, ModuleItem> units; |
| if (catalog != null) { |
| try { |
| units = AutoUpdateCatalogParser.getUpdateItems(catalog, catalog, this); |
| } catch (IOException ex) { |
| throw new BuildException(ex.getMessage(), ex); |
| } |
| } else { |
| if (nbmSet == null) { |
| throw new BuildException("Specify updatecenter or list of NBMs"); |
| } |
| DirectoryScanner s = nbmSet.getDirectoryScanner(getProject()); |
| File basedir = s.getBasedir(); |
| units = new HashMap<>(); |
| for (String incl : s.getIncludedFiles()) { |
| File nbm = new File(basedir, incl); |
| try { |
| URL u = new URL("jar:" + nbm.toURI() + "!/Info/info.xml"); |
| Map<String, ModuleItem> map; |
| final URL url = nbm.toURI().toURL(); |
| try { |
| map = AutoUpdateCatalogParser.getUpdateItems(u, url, this); |
| } catch (FileNotFoundException ex) { |
| JarFile f = new JarFile(nbm); |
| Document doc = XMLUtil.createDocument("module"); |
| MakeUpdateDesc.fakeOSGiInfoXml(f, nbm, doc); |
| ByteArrayOutputStream os = new ByteArrayOutputStream(); |
| XMLUtil.write(doc, os); |
| ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); |
| map = AutoUpdateCatalogParser.getUpdateItems(u, is, url, this); |
| } |
| assert map.size() == 1; |
| Map.Entry<String, ModuleItem> entry = map.entrySet().iterator().next(); |
| units.put(entry.getKey(), entry.getValue().changeDistribution(url)); |
| } catch (IOException ex) { |
| throw new BuildException(ex); |
| } |
| } |
| } |
| |
| Map<String,List<String>> installed; |
| if (dir != null) { |
| if (!dir.exists()) { |
| dir.mkdirs(); |
| } |
| File[] arr = dir.listFiles(); |
| if (arr == null) { |
| throw new BuildException("installdir must be existing directory: " + dir); |
| } |
| installed = findExistingModules(arr); |
| } else { |
| installed = findExistingModules(cluster); |
| } |
| |
| |
| for (ModuleItem uu : units.values()) { |
| if (!matches(uu.getCodeName(), uu.targetcluster)) { |
| continue; |
| } |
| log("found module: " + uu, Project.MSG_VERBOSE); |
| List<String> info = installed.get(uu.getCodeName()); |
| if (info != null && !uu.isNewerThan(info.get(0))) { |
| log("Version " + info.get(0) + " of " + uu.getCodeName() + " is up to date", Project.MSG_VERBOSE); |
| if (!force) { |
| continue; |
| } |
| } |
| |
| byte[] bytes = new byte[4096]; |
| File tmp = null; |
| boolean delete = false; |
| File lastM = null; |
| try { |
| if (download == null && uu.getURL().getProtocol().equals("file")) { |
| try { |
| tmp = new File(uu.getURL().toURI()); |
| } catch (URISyntaxException ex) { |
| tmp = null; |
| } |
| if (!tmp.exists()) { |
| tmp = null; |
| } |
| } |
| final String dash = uu.getCodeName().replace('.', '-'); |
| if (tmp == null) { |
| if (download != null) { |
| tmp = new File(download, dash + ".nbm"); |
| String v = readVersion(tmp); |
| if (v != null && !uu.isNewerThan(v)) { |
| log("Version " + v + " of " + tmp + " is up to date", Project.MSG_VERBOSE); |
| if (!force) { |
| continue; |
| } |
| } |
| } else { |
| tmp = File.createTempFile(dash, ".nbm"); |
| tmp.deleteOnExit(); |
| delete = true; |
| } |
| if (info == null) { |
| log(uu.getCodeName() + " is not present, downloading version " + uu.getSpecVersion(), Project.MSG_INFO); |
| } else { |
| log("Version " + info.get(0) + " of " + uu.getCodeName() + " needs update to " + uu.getSpecVersion(), Project.MSG_INFO); |
| } |
| Get get = new Get(); |
| get.setProject(getProject()); |
| get.setTaskName("get:" + uu.getCodeName()); |
| get.setSrc(uu.getURL()); |
| get.setDest(tmp); |
| get.setVerbose(true); |
| get.execute(); |
| } |
| if (downloadOnly) { |
| continue; |
| } |
| |
| File whereTo; |
| if (dir != null) { |
| if (uu.targetcluster == null) { |
| throw new BuildException("Specify todir, not installdir, since " + dash + ".nbm does not define target cluster", getLocation()); |
| } |
| whereTo = new File(dir, uu.targetcluster); |
| } else { |
| whereTo = cluster; |
| } |
| whereTo.mkdirs(); |
| lastM = new File(whereTo, ".lastModified"); |
| lastM.createNewFile(); |
| |
| if (info != null) { |
| for (int i = 1; i < info.size(); i++) { |
| File oldFile = new File(whereTo, info.get(i).replace('/', File.separatorChar)); |
| oldFile.delete(); |
| } |
| } |
| |
| Document doc = XMLUtil.createDocument("module"); |
| Element module = doc.getDocumentElement(); |
| module.setAttribute("codename", uu.getCodeName()); |
| Element module_version = (Element) module.appendChild(doc.createElement("module_version")); |
| module_version.setAttribute("install_time", String.valueOf(System.currentTimeMillis())); |
| module_version.setAttribute("last", "true"); |
| module_version.setAttribute("origin", "Ant"); // XXX set to URL origin |
| module_version.setAttribute("specification_version", uu.getSpecVersion()); |
| |
| try (JarFile zf = new JarFile(tmp)) { |
| Manifest manifest = zf.getManifest(); |
| if (manifest == null || manifest.getMainAttributes().getValue("Bundle-SymbolicName") == null) { // regular NBM |
| Enumeration<? extends ZipEntry> en = zf.entries(); |
| while (en.hasMoreElements()) { |
| ZipEntry zipEntry = en.nextElement(); |
| if (!zipEntry.getName().startsWith("netbeans/")) { |
| continue; |
| } |
| if (zipEntry.getName().endsWith("/")) { |
| continue; |
| } |
| String relName = zipEntry.getName().substring(9); |
| File trgt = new File(whereTo, relName.replace('/', File.separatorChar)); |
| trgt.getParentFile().mkdirs(); |
| log("Writing " + trgt, Project.MSG_VERBOSE); |
| |
| InputStream is = zf.getInputStream(zipEntry); |
| boolean doUnpack200 = false; |
| if(relName.endsWith(".jar.pack.gz") && zf.getEntry(zipEntry.getName().substring(0, zipEntry.getName().length() - 8))==null) { |
| doUnpack200 = true; |
| } |
| OutputStream os; |
| AtomicLong assumedCRC = null; |
| if (relName.endsWith(".external")) { |
| assumedCRC = new AtomicLong(); |
| is = externalDownload(is, assumedCRC); |
| if (assumedCRC.longValue() == -1L) { |
| assumedCRC = null; |
| } |
| File dest = new File(trgt.getParentFile(), trgt.getName().substring(0, trgt.getName().length() - 9)); |
| os = new FileOutputStream(dest); |
| relName = relName.substring(0, relName.length() - ".external".length()); |
| } else { |
| os = new FileOutputStream(trgt); |
| } |
| CRC32 crc = new CRC32(); |
| for (;;) { |
| int len = is.read(bytes); |
| if (len == -1) { |
| break; |
| } |
| if(!doUnpack200) { |
| crc.update(bytes, 0, len); |
| } |
| os.write(bytes, 0, len); |
| } |
| is.close(); |
| os.close(); |
| long crcValue = crc.getValue(); |
| if(doUnpack200) { |
| File dest = new File(trgt.getParentFile(), trgt.getName().substring(0, trgt.getName().length() - 8)); |
| log("Unpacking " + trgt + " to " + dest, Project.MSG_VERBOSE); |
| unpack200(trgt, dest); |
| trgt.delete(); |
| crcValue = getFileCRC(dest); |
| relName = relName.substring(0, relName.length() - 8); |
| } |
| if (assumedCRC != null && assumedCRC.get() != crcValue) { |
| throw new BuildException("Expecting CRC " + assumedCRC.get() + " but was " + crcValue); |
| } |
| Element file = (Element) module_version.appendChild(doc.createElement("file")); |
| file.setAttribute("crc", String.valueOf(crcValue)); |
| file.setAttribute("name", relName); |
| } |
| } else { // OSGi |
| String relName = "config/Modules/" + dash + ".xml"; |
| File configModulesXml = new File(whereTo, relName); |
| configModulesXml.getParentFile().mkdirs(); |
| try (PrintWriter w = new PrintWriter(configModulesXml)) { |
| w.print("<?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=\""); |
| w.print(uu.getCodeName()); |
| w.print("\">\n <param name=\"autoload\">true</param>\n <param name=\"eager\">false</param>\n <param name=\"jar\">modules/"); |
| w.print(dash); |
| w.print(".jar</param>\n <param name=\"reloadable\">false</param>\n</module>\n"); |
| } |
| Element file = (Element) module_version.appendChild(doc.createElement("file")); |
| file.setAttribute("crc", String.valueOf(getFileCRC(configModulesXml))); |
| file.setAttribute("name", relName); |
| relName = "modules/" + dash + ".jar"; |
| File bundle = new File(whereTo, relName); |
| bundle.getParentFile().mkdirs(); |
| FileUtils.getFileUtils().copyFile(tmp, bundle); |
| file = (Element) module_version.appendChild(doc.createElement("file")); |
| file.setAttribute("crc", String.valueOf(getFileCRC(bundle))); |
| file.setAttribute("name", relName); |
| } |
| } |
| File tracking = new File(new File(whereTo, "update_tracking"), dash + ".xml"); |
| log("Writing tracking file " + tracking, Project.MSG_VERBOSE); |
| tracking.getParentFile().mkdirs(); |
| OutputStream config = new FileOutputStream(tracking); |
| try { |
| XMLUtil.write(doc, config); |
| } finally { |
| config.close(); |
| } |
| } catch (IOException ex) { |
| throw new BuildException(ex); |
| } finally { |
| if (delete && tmp != null) { |
| tmp.delete(); |
| } |
| if (lastM != null) { |
| lastM.setLastModified(System.currentTimeMillis()); |
| } |
| } |
| } |
| } |
| |
| private InputStream externalDownload(InputStream is, AtomicLong crc) throws IOException { |
| BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); |
| URLConnection conn; |
| crc.set(-1L); |
| for (;;) { |
| String line = br.readLine(); |
| if (line == null) { |
| break; |
| } |
| if (line.startsWith("CRC:")) { |
| crc.set(Long.parseLong(line.substring(4).trim())); |
| } |
| if (line.startsWith("URL:")) { |
| String url = line.substring(4).trim(); |
| for (;;) { |
| int index = url.indexOf("${"); |
| if (index == -1) { |
| break; |
| } |
| int end = url.indexOf("}", index); |
| String propName = url.substring(index + 2, end); |
| final String propVal = System.getProperty(propName); |
| if (propVal == null) { |
| throw new IOException("Can't find property " + propName); |
| } |
| url = url.substring(0, index) + propVal + url.substring(end + 1); |
| } |
| try { |
| URI u = new URI(url); |
| if ("m2".equals(u.getScheme())) { |
| log("Trying external Maven URL: " + u, Project.MSG_INFO); |
| return DownloadBinaries.downloadMaven(this, u); |
| } |
| log("Trying external URL: " + u, Project.MSG_INFO); |
| conn = u.toURL().openConnection(); |
| conn.connect(); |
| return conn.getInputStream(); |
| } catch (URISyntaxException | IOException ex) { |
| log("Cannot connect to " + url, Project.MSG_WARN); |
| try { |
| logThrowable(ex); |
| } catch (LinkageError err) { |
| ex.printStackTrace(); |
| } |
| } |
| } |
| } |
| throw new IOException("Cannot resolve external references"); |
| } |
| |
| private void logThrowable(Exception ex) { |
| log("Details", ex, Project.MSG_VERBOSE); |
| } |
| |
| |
| public static boolean unpack200(File src, File dest) { |
| // Copy of ModuleUpdater.unpack200 |
| String unpack200Executable = new File(System.getProperty("java.home"), |
| "bin/unpack200" + (isWindows() ? ".exe" : "")).getAbsolutePath(); |
| ProcessBuilder pb = new ProcessBuilder(unpack200Executable, src.getAbsolutePath(), dest.getAbsolutePath()); |
| pb.directory(src.getParentFile()); |
| int result = 1; |
| try { |
| //maybe reuse start() method here? |
| Process process = pb.start(); |
| //TODO: Need to think of unpack200/lvprcsrv.exe issues |
| //https://netbeans.org/bugzilla/show_bug.cgi?id=117334 |
| //https://netbeans.org/bugzilla/show_bug.cgi?id=119861 |
| result = process.waitFor(); |
| process.destroy(); |
| } catch (IOException | InterruptedException e) { |
| e.printStackTrace(); |
| } |
| return result == 0; |
| } |
| private static boolean isWindows() { |
| String os = System.getProperty("os.name"); // NOI18N |
| return (os != null && os.toLowerCase().startsWith("windows"));//NOI18N |
| } |
| |
| private static long getFileCRC(File file) throws IOException { |
| BufferedInputStream bsrc = null; |
| CRC32 crc = new CRC32(); |
| try { |
| bsrc = new BufferedInputStream( new FileInputStream( file ) ); |
| byte[] bytes = new byte[1024]; |
| int i; |
| while( (i = bsrc.read(bytes)) != -1 ) { |
| crc.update(bytes, 0, i ); |
| } |
| } |
| finally { |
| if ( bsrc != null ) |
| bsrc.close(); |
| } |
| return crc.getValue(); |
| } |
| |
| private boolean matches(String cnb, String targetCluster) { |
| for (Modules ps : modules) { |
| if (ps.clusters != null) { |
| if (targetCluster == null) { |
| continue; |
| } |
| if (!ps.clusters.matcher(targetCluster).matches()) { |
| continue; |
| } |
| } |
| |
| if (ps.pattern.matcher(cnb).matches()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private Map<String,List<String>> findExistingModules(File... clusters) { |
| Map<String,List<String>> all = new HashMap<>(); |
| for (File c : clusters) { |
| File mc = new File(c, "update_tracking"); |
| final File[] arr = mc.listFiles(); |
| if (arr == null) { |
| continue; |
| } |
| for (File m : arr) { |
| try { |
| parseVersion(m, all); |
| } catch (Exception ex) { |
| log("Cannot parse " + m, ex, Project.MSG_WARN); |
| } |
| } |
| } |
| return all; |
| } |
| |
| private String readVersion(File nbm) { |
| String infoXml = "jar:" + nbm.toURI() + "!/Info/info.xml"; |
| try { |
| XPathFactory f = XPathFactory.newInstance(); |
| Document doc = XMLUtil.parse(new InputSource(infoXml), false, false, XMLUtil.rethrowHandler(), XMLUtil.nullResolver()); |
| String res = f.newXPath().evaluate("module/manifest/@OpenIDE-Module-Specification-Version", doc); |
| if (res.length() == 0) { |
| throw new IOException("Not found tag OpenIDE-Module-Specification-Version!"); |
| } |
| return res; |
| } catch (Exception ex) { |
| log("Cannot parse " + infoXml + ": " + ex, ex, Project.MSG_WARN); |
| return null; |
| } |
| } |
| |
| private void parseVersion(final File config, final Map<String,List<String>> toAdd) throws Exception { |
| class P extends DefaultHandler { |
| String name; |
| List<String> arr; |
| |
| @Override |
| public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { |
| if ("module".equals(qName)) { |
| name = attributes.getValue("codename"); |
| int slash = name.indexOf('/'); |
| if (slash > 0) { |
| name = name.substring(0, slash); |
| } |
| return; |
| } |
| if ("module_version".equals(qName)) { |
| String version = attributes.getValue("specification_version"); |
| if (name == null || version == null) { |
| throw new BuildException("Cannot find version in " + config); |
| } |
| arr = new ArrayList<>(); |
| arr.add(version); |
| toAdd.put(name, arr); |
| return; |
| } |
| if ("file".equals(qName)) { |
| arr.add(attributes.getValue("name")); |
| } |
| } |
| |
| @Override |
| public InputSource resolveEntity(String string, String string1) throws IOException, SAXException { |
| return new InputSource(new ByteArrayInputStream(new byte[0])); |
| } |
| } |
| P p = new P(); |
| SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); |
| parser.parse(config, p); |
| } |
| |
| public static final class Modules { |
| Pattern pattern; |
| Pattern clusters; |
| |
| public void setIncludes(String regExp) { |
| pattern = Pattern.compile(regExp); |
| } |
| |
| public void setClusters(String regExp) { |
| clusters = Pattern.compile(regExp); |
| } |
| } |
| } |