| /* |
| * 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.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.text.DateFormat; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Stack; |
| import java.util.jar.Manifest; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.zip.GZIPInputStream; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.EntityResolver; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * |
| * @author Jiri Rechtacek |
| */ |
| class AutoUpdateCatalogParser extends DefaultHandler { |
| private final Map<String, ModuleItem> items; |
| private final URL provider; |
| private final EntityResolver entityResolver; |
| private final URI baseUri; |
| |
| private AutoUpdateCatalogParser (Map<String, ModuleItem> items, URL provider, URI base) { |
| this.items = items; |
| this.provider = provider; |
| this.entityResolver = newEntityResolver(); |
| this.baseUri = base; |
| } |
| |
| private EntityResolver newEntityResolver () { |
| return new EntityResolver() { |
| public InputSource resolveEntity(String string, String string1) throws SAXException, IOException { |
| return new InputSource(new ByteArrayInputStream(new byte[0])); |
| } |
| }; |
| } |
| |
| |
| private static final Logger ERR = Logger.getLogger (AutoUpdateCatalogParser.class.getName ()); |
| |
| private static enum ELEMENTS { |
| module_updates, module_group, notification, module, description, |
| module_notification, external_package, manifest, l10n, license, |
| message_digest, UNKNOWN; |
| |
| private static ELEMENTS valueOfOrUnknown(String qName) { |
| try { |
| return valueOf(qName); |
| } catch (IllegalArgumentException ex) { |
| return UNKNOWN; |
| } |
| } |
| } |
| |
| private static final String MODULE_UPDATES_ATTR_TIMESTAMP = "timestamp"; // NOI18N |
| |
| private static final String MODULE_GROUP_ATTR_NAME = "name"; // NOI18N |
| |
| private static final String NOTIFICATION_ATTR_URL = "url"; // NOI18N |
| |
| private static final String LICENSE_ATTR_NAME = "name"; // NOI18N |
| |
| private static final String MODULE_ATTR_CODE_NAME_BASE = "codenamebase"; // NOI18N |
| private static final String MODULE_ATTR_HOMEPAGE = "homepage"; // NOI18N |
| private static final String MODULE_ATTR_DISTRIBUTION = "distribution"; // NOI18N |
| private static final String MODULE_ATTR_DOWNLOAD_SIZE = "downloadsize"; // NOI18N |
| private static final String MODULE_ATTR_NEEDS_RESTART = "needsrestart"; // NOI18N |
| private static final String MODULE_ATTR_MODULE_AUTHOR = "moduleauthor"; // NOI18N |
| private static final String MODULE_ATTR_RELEASE_DATE = "releasedate"; // NOI18N |
| private static final String MODULE_ATTR_IS_GLOBAL = "global"; // NOI18N |
| private static final String MODULE_ATTR_TARGET_CLUSTER = "targetcluster"; // NOI18N |
| private static final String MODULE_ATTR_EAGER = "eager"; // NOI18N |
| private static final String MODULE_ATTR_AUTOLOAD = "autoload"; // NOI18N |
| private static final String MODULE_ATTR_LICENSE = "license"; // NOI18N |
| private static final String LICENSE_ATTR_URL = "url"; // NOI18N |
| |
| private static final String MANIFEST_ATTR_SPECIFICATION_VERSION = "OpenIDE-Module-Specification-Version"; // NOI18N |
| |
| private static final String TIME_STAMP_FORMAT = "ss/mm/hh/dd/MM/yyyy"; // NOI18N |
| |
| private static final String L10N_ATTR_LOCALE = "langcode"; // NOI18N |
| private static final String L10N_ATTR_BRANDING = "brandingcode"; // NOI18N |
| private static final String L10N_ATTR_MODULE_SPECIFICATION = "module_spec_version"; // NOI18N |
| private static final String L10N_ATTR_MODULE_MAJOR_VERSION = "module_major_version"; // NOI18N |
| private static final String L10N_ATTR_LOCALIZED_MODULE_NAME = "OpenIDE-Module-Name"; // NOI18N |
| private static final String L10N_ATTR_LOCALIZED_MODULE_DESCRIPTION = "OpenIDE-Module-Long-Description"; // NOI18N |
| |
| private static String GZIP_EXTENSION = ".gz"; // NOI18N |
| |
| private static Map<String, ModuleItem> cache; |
| private static URI cacheURI; |
| static synchronized Map<String, ModuleItem> getUpdateItems (URL url, URL provider, Task task) throws IOException { |
| return getUpdateItems(url, null, provider, task); |
| } |
| static synchronized Map<String, ModuleItem> getUpdateItems (URL url, InputStream data, URL provider, Task task) throws IOException { |
| |
| Map<String, ModuleItem> items = new HashMap<> (); |
| URI base; |
| try { |
| if (provider != null) { |
| base = provider.toURI(); |
| } else { |
| base = url.toURI(); |
| } |
| if (cache != null && cacheURI.equals(base)) { |
| task.log("Using existing module item cache " + base, Project.MSG_INFO); |
| return cache; |
| } |
| task.log("Downloading " + base, Project.MSG_INFO); |
| InputSource is = null; |
| try { |
| SAXParserFactory factory = SAXParserFactory.newInstance(); |
| factory.setValidating(false); |
| SAXParser saxParser = factory.newSAXParser(); |
| is = getInputSource(url, data, provider, base); |
| saxParser.parse(is, new AutoUpdateCatalogParser(items, provider, base)); |
| cacheURI = base; |
| cache = items; |
| } catch (IOException ex) { |
| throw ex; |
| } catch (Exception ex) { |
| throw (IOException)new IOException(ex.getMessage()).initCause(ex); |
| } finally { |
| if (is != null && is.getByteStream() != null) { |
| try { |
| is.getByteStream().close(); |
| } catch (IOException e) { |
| } |
| } |
| } |
| } catch (URISyntaxException ex) { |
| ERR.log(Level.INFO, null, ex); |
| } |
| return items; |
| } |
| |
| private static boolean isGzip (URL url) { |
| boolean res = false; |
| if (url != null) { |
| res = url.getPath ().toLowerCase ().endsWith (GZIP_EXTENSION); |
| ERR.log (Level.FINER, "Is GZIP " + url + " ? " + res); |
| } else { |
| ERR.log (Level.WARNING, "AutoupdateCatalogProvider has not URL."); |
| } |
| return res; |
| } |
| |
| private static InputSource getInputSource(URL toParse, InputStream is, URL p, URI base) throws IOException { |
| if (is == null) { |
| is = toParse.openStream(); |
| } |
| if (isGzip (p)) { |
| try { |
| is = new GZIPInputStream(is); |
| } catch (IOException e) { |
| ERR.log (Level.INFO, |
| "The file at " + toParse + |
| ", corresponding to the catalog at " + p + |
| ", does not look like the gzip file, trying to parse it as the pure xml" , e); |
| //#150034 |
| // Sometimes the .xml.gz file is downloaded as the pure .xml file due to the strange content-encoding processing |
| is.close(); |
| is = null; |
| is = toParse.openStream(); |
| } |
| } |
| InputSource src = new InputSource(new BufferedInputStream (is)); |
| src.setSystemId(base.toString()); |
| return src; |
| } |
| |
| private Stack<String> currentGroup = new Stack<> (); |
| private String catalogDate; |
| private Stack<ModuleDescriptor> currentModule = new Stack<> (); |
| private Stack<Map <String,String>> currentLicense = new Stack<> (); |
| private Stack<String> currentNotificationUrl = new Stack<> (); |
| private List<String> lines = new ArrayList<> (); |
| private int bufferInitSize = 0; |
| |
| @Override |
| public void characters (char[] ch, int start, int length) throws SAXException { |
| lines.add (new String(ch, start, length)); |
| bufferInitSize += length; |
| } |
| |
| @Override |
| public void endElement (String uri, String localName, String qName) throws SAXException { |
| switch (ELEMENTS.valueOfOrUnknown(qName)) { |
| case module_updates : |
| break; |
| case module_group : |
| assert ! currentGroup.empty () : "Premature end of module_group " + qName; |
| currentGroup.pop (); |
| break; |
| case module : |
| assert ! currentModule.empty () : "Premature end of module " + qName; |
| currentModule.pop (); |
| break; |
| case l10n : |
| break; |
| case manifest : |
| break; |
| case message_digest: |
| break; |
| case description : |
| ERR.info ("Not supported yet."); |
| break; |
| case notification : |
| // write catalog notification |
| if (this.provider != null && ! lines.isEmpty ()) { |
| StringBuffer sb = new StringBuffer (bufferInitSize); |
| for (String line : lines) { |
| sb.append (line); |
| } |
| String notification = sb.toString (); |
| String notificationUrl = currentNotificationUrl.peek (); |
| if (notificationUrl != null && notificationUrl.length () > 0) { |
| notification += (notification.length () > 0 ? "<br>" : "") + // NOI18N |
| "<a name=\"autoupdate_catalog_parser\" href=\"" + notificationUrl + "\">" + notificationUrl + "</a>"; // NOI18N |
| } else { |
| notification += (notification.length () > 0 ? "<br>" : "") + |
| "<a name=\"autoupdate_catalog_parser\"/>"; // NOI18N |
| } |
| } |
| currentNotificationUrl.pop (); |
| break; |
| case module_notification : |
| // write module notification |
| if (! lines.isEmpty ()) { |
| ModuleDescriptor md = currentModule.peek (); |
| assert md != null : "ModuleDescriptor found for " + provider; |
| StringBuffer buf = new StringBuffer (bufferInitSize); |
| for (String line : lines) { |
| buf.append (line); |
| } |
| md.appendNotification (buf.toString ()); |
| } |
| break; |
| case external_package : |
| ERR.info ("Not supported yet."); |
| break; |
| case license : |
| assert ! currentLicense.empty () : "Premature end of license " + qName; |
| Map <String, String> curLic = currentLicense.peek (); |
| String licenseName = curLic.keySet().iterator().next(); |
| Collection<String> values = curLic.values(); |
| String licenseUrl = (values.size() > 0) ? values.iterator().next() : null; |
| |
| currentLicense.pop (); |
| break; |
| default: |
| ERR.warning ("Unknown element " + qName); |
| } |
| } |
| |
| @Override |
| public void endDocument () throws SAXException { |
| ERR.fine ("End parsing " + (provider == null ? "" : provider) + " at " + System.currentTimeMillis ()); |
| } |
| |
| @Override |
| public void startDocument () throws SAXException { |
| ERR.fine ("Start parsing " + (provider == null ? "" : provider) + " at " + System.currentTimeMillis ()); |
| } |
| |
| @Override |
| public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException { |
| lines.clear(); |
| bufferInitSize = 0; |
| switch (ELEMENTS.valueOfOrUnknown(qName)) { |
| case module_updates : |
| try { |
| catalogDate = ""; |
| DateFormat format = new SimpleDateFormat (TIME_STAMP_FORMAT); |
| String timeStamp = attributes.getValue (MODULE_UPDATES_ATTR_TIMESTAMP); |
| if (timeStamp == null) { |
| ERR.info ("No timestamp is presented in " + (this.provider == null ? "" : this.provider)); |
| } else { |
| //catalogDate = Utilities.formatDate (format.parse (timeStamp)); |
| catalogDate = format.parse (timeStamp).toString(); |
| ERR.finer ("Successfully read time " + timeStamp); // NOI18N |
| } |
| } catch (ParseException pe) { |
| ERR.log (Level.INFO, null, pe); |
| } |
| break; |
| case module_group : |
| currentGroup.push (attributes.getValue (MODULE_GROUP_ATTR_NAME)); |
| break; |
| case module : |
| ModuleDescriptor md = ModuleDescriptor.getModuleDescriptor ( |
| currentGroup.size () > 0 ? currentGroup.peek () : null, /* group */ |
| baseUri, /* base URI */ |
| this.catalogDate); /* catalog date */ |
| md.appendModuleAttributes (attributes); |
| currentModule.push (md); |
| break; |
| case l10n : |
| // construct l10n |
| // XXX |
| break; |
| case manifest : |
| |
| // construct module |
| ModuleDescriptor desc = currentModule.peek (); |
| desc.appendManifest (attributes); |
| ModuleItem m = desc.createUpdateItem (); |
| |
| // put module into UpdateItems |
| items.put (desc.getId (), m); |
| |
| break; |
| case message_digest: |
| break; |
| case description : |
| ERR.info ("Not supported yet."); |
| break; |
| case module_notification : |
| break; |
| case notification : |
| currentNotificationUrl.push (attributes.getValue (NOTIFICATION_ATTR_URL)); |
| break; |
| case external_package : |
| ERR.info ("Not supported yet."); |
| break; |
| case license : |
| Map <String, String> map = new HashMap<> (); |
| map.put(attributes.getValue (LICENSE_ATTR_NAME), attributes.getValue (LICENSE_ATTR_URL)); |
| currentLicense.push (map); |
| break; |
| default: |
| ERR.warning ("Unknown element " + qName); |
| } |
| } |
| |
| @Override |
| public void warning(SAXParseException e) throws SAXException { |
| parseError(e); |
| } |
| |
| @Override |
| public void error(SAXParseException e) throws SAXException { |
| parseError(e); |
| } |
| |
| @Override |
| public void fatalError(SAXParseException e) throws SAXException { |
| parseError(e); |
| } |
| |
| private void parseError(SAXParseException e) { |
| ERR.warning(e.getSystemId() + ":" + e.getLineNumber() + ":" + e.getColumnNumber() + ": " + e.getLocalizedMessage()); |
| } |
| |
| @Override |
| public InputSource resolveEntity (String publicId, String systemId) throws IOException, SAXException { |
| return entityResolver.resolveEntity (publicId, systemId); |
| } |
| |
| private static class ModuleDescriptor { |
| private String moduleCodeName; |
| private URL distributionURL; |
| private String targetcluster; |
| private String homepage; |
| private String downloadSize; |
| private String author; |
| private String publishDate; |
| private String notification; |
| |
| private Boolean needsRestart; |
| private Boolean isGlobal; |
| private Boolean isEager; |
| private Boolean isAutoload; |
| |
| private String specVersion; |
| private Manifest mf; |
| |
| private String id; |
| |
| private String group; |
| private URI base; |
| private String catalogDate; |
| |
| private static ModuleDescriptor md = null; |
| |
| private ModuleDescriptor () {} |
| |
| public static ModuleDescriptor getModuleDescriptor (String group, URI base, String catalogDate) { |
| if (md == null) { |
| md = new ModuleDescriptor (); |
| } |
| |
| md.group = group; |
| md.base = base; |
| md.catalogDate = catalogDate; |
| |
| return md; |
| } |
| |
| public void appendModuleAttributes (Attributes module) { |
| moduleCodeName = module.getValue (MODULE_ATTR_CODE_NAME_BASE); |
| distributionURL = getDistribution (module.getValue (MODULE_ATTR_DISTRIBUTION), base); |
| targetcluster = module.getValue (MODULE_ATTR_TARGET_CLUSTER); |
| homepage = module.getValue (MODULE_ATTR_HOMEPAGE); |
| downloadSize = module.getValue (MODULE_ATTR_DOWNLOAD_SIZE); |
| author = module.getValue (MODULE_ATTR_MODULE_AUTHOR); |
| publishDate = module.getValue (MODULE_ATTR_RELEASE_DATE); |
| if (publishDate == null || publishDate.length () == 0) { |
| publishDate = catalogDate; |
| } |
| String needsrestart = module.getValue (MODULE_ATTR_NEEDS_RESTART); |
| String global = module.getValue (MODULE_ATTR_IS_GLOBAL); |
| String eager = module.getValue (MODULE_ATTR_EAGER); |
| String autoload = module.getValue (MODULE_ATTR_AUTOLOAD); |
| |
| needsRestart = needsrestart == null || needsrestart.trim ().length () == 0 ? null : Boolean.valueOf (needsrestart); |
| isGlobal = global == null || global.trim ().length () == 0 ? null : Boolean.valueOf (global); |
| isEager = Boolean.parseBoolean (eager); |
| isAutoload = Boolean.parseBoolean (autoload); |
| |
| String licName = module.getValue (MODULE_ATTR_LICENSE); |
| } |
| |
| public void appendManifest (Attributes manifest) { |
| specVersion = manifest.getValue (MANIFEST_ATTR_SPECIFICATION_VERSION); |
| mf = getManifest (manifest); |
| id = moduleCodeName + '_' + specVersion; // NOI18N |
| } |
| |
| public void appendNotification (String notification) { |
| this.notification = notification; |
| } |
| |
| public String getId () { |
| return id; |
| } |
| |
| public ModuleItem createUpdateItem () { |
| ModuleItem res = ModuleItem.createModule ( |
| moduleCodeName, |
| specVersion, |
| distributionURL, |
| author, |
| downloadSize, |
| homepage, |
| publishDate, |
| group, |
| mf, |
| isEager, |
| isAutoload, |
| needsRestart, |
| isGlobal, |
| targetcluster, |
| null); |
| |
| // clean-up ModuleDescriptor |
| cleanUp (); |
| |
| return res; |
| } |
| |
| private void cleanUp (){ |
| this.specVersion = null; |
| this.mf = null; |
| this.notification = null; |
| } |
| } |
| |
| private static URL getDistribution (String distribution, URI base) { |
| URL retval = null; |
| if (distribution != null && distribution.length () > 0) { |
| try { |
| URI distributionURI = new URI (distribution); |
| if (! distributionURI.isAbsolute ()) { |
| if (base != null) { |
| distributionURI = base.resolve (distributionURI); |
| } |
| } |
| retval = distributionURI.toURL (); |
| } catch (MalformedURLException | URISyntaxException ex) { |
| ERR.log (Level.INFO, null, ex); |
| } |
| } |
| return retval; |
| } |
| |
| private static Manifest getManifest (Attributes attrList) { |
| Manifest mf = new Manifest (); |
| java.util.jar.Attributes mfAttrs = mf.getMainAttributes (); |
| |
| for (int i = 0; i < attrList.getLength (); i++) { |
| mfAttrs.put (new java.util.jar.Attributes.Name (attrList.getQName (i)), attrList.getValue (i)); |
| } |
| return mf; |
| } |
| |
| public static final class ModuleItem { |
| private final String moduleCodeName; |
| private final String specVersion; |
| private final URL distributionURL; |
| public final String targetcluster; |
| |
| private ModuleItem(String moduleCodeName, String specVersion, URL distributionURL, String targetcluster) { |
| this.moduleCodeName = moduleCodeName; |
| this.specVersion = specVersion; |
| this.distributionURL = distributionURL; |
| this.targetcluster = targetcluster; |
| } |
| |
| ModuleItem changeDistribution(URL u) { |
| return new ModuleItem(moduleCodeName, specVersion, u, targetcluster); |
| } |
| |
| |
| static ModuleItem createModule( |
| String moduleCodeName, |
| String specVersion, |
| URL distributionURL, |
| String author, |
| String downloadSize, |
| String homepage, |
| String publishDate, |
| String group, |
| Manifest mf, |
| Boolean eager, |
| Boolean autoload, |
| Boolean needsRestart, |
| Boolean global, |
| String targetcluster, |
| Object object |
| ) { |
| return new ModuleItem(moduleCodeName, specVersion, distributionURL, targetcluster); |
| } |
| |
| public String getCodeName() { |
| return moduleCodeName; |
| } |
| |
| String getSpecVersion() { |
| return specVersion; |
| } |
| |
| URL getURL() { |
| return distributionURL; |
| } |
| |
| @Override |
| public String toString() { |
| return "[" + moduleCodeName + "@" + specVersion + "(" + targetcluster + ") <- " + distributionURL + "]"; |
| } |
| |
| boolean isNewerThan(String version) { |
| String[] mine = specVersion.split("\\."); |
| String[] its = version.split("\\."); |
| |
| int min = Math.min(mine.length, its.length); |
| for (int i = 0; i < min; i++) { |
| int m = Integer.parseInt(mine[i]); |
| int it = Integer.parseInt(its[i]); |
| |
| if (m > it) { |
| return true; |
| } |
| if (m < it) { |
| return false; |
| } |
| } |
| return mine.length > its.length; |
| } |
| } |
| } |