All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.xmlbeans.impl.tool.BaseSchemaResourceManager Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*   Copyright 2004 The Apache Software Foundation
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.xmlbeans.impl.tool;

import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.impl.common.IOUtil;
import org.apache.xmlbeans.impl.util.HexBin;
import org.apache.xmlbeans.impl.xb.xsdownload.DownloadedSchemaEntry;
import org.apache.xmlbeans.impl.xb.xsdownload.DownloadedSchemasDocument;
import org.apache.xmlbeans.impl.xb.xsdownload.DownloadedSchemasDocument.DownloadedSchemas;
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument;
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument.Schema;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public abstract class BaseSchemaResourceManager extends SchemaImportResolver {
    private static final String USER_AGENT = "XMLBeans/" + XmlBeans.getVersion() + " (" + XmlBeans.getTitle() + ")";

    private String _defaultCopyDirectory;
    private DownloadedSchemasDocument _importsDoc;
    private final Map _resourceForFilename = new HashMap<>();
    private final Map _resourceForURL = new HashMap<>();
    private final Map _resourceForNamespace = new HashMap<>();
    private final Map _resourceForDigest = new HashMap<>();
    private final Map _resourceForCacheEntry = new HashMap<>();
    private Set _redownloadSet = new HashSet<>();

    protected BaseSchemaResourceManager() {
        // concrete subclasses should call init in their constructors
    }

    protected final void init() {
        if (fileExists(getIndexFilename())) {
            try {
                _importsDoc = DownloadedSchemasDocument.Factory.parse(inputStreamForFile(getIndexFilename()));
            } catch (IOException e) {
                _importsDoc = null;
            } catch (Exception e) {
                throw new IllegalStateException("Problem reading xsdownload.xml: please fix or delete this file", e);
            }
        }
        if (_importsDoc == null) {
            try {
                _importsDoc = DownloadedSchemasDocument.Factory.parse(
                    ""
                );
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }

        String defaultDir = _importsDoc.getDownloadedSchemas().getDefaultDirectory();
        if (defaultDir == null) {
            defaultDir = getDefaultSchemaDir();
        }
        _defaultCopyDirectory = defaultDir;

        // now initialize data structures
        DownloadedSchemaEntry[] entries = _importsDoc.getDownloadedSchemas().getEntryArray();
        for (DownloadedSchemaEntry entry : entries) {
            updateResource(entry);
        }
    }

    public final void writeCache() throws IOException {
        InputStream input = _importsDoc.newInputStream(new XmlOptions().setSavePrettyPrint());
        writeInputStreamToFile(input, getIndexFilename());
    }

    public final void processAll(boolean sync, boolean refresh, boolean imports) {
        _redownloadSet = refresh ? new HashSet<>() : null;

        String[] allFilenames = getAllXSDFilenames();

        if (sync) {
            syncCacheWithLocalXsdFiles(allFilenames, false);
        }

        SchemaResource[] starters = _resourceForFilename.values().toArray(new SchemaResource[0]);

        if (refresh) {
            redownloadEntries(starters);
        }

        if (imports) {
            resolveImports(starters);
        }

        _redownloadSet = null;
    }

    public final void process(String[] uris, String[] filenames, boolean sync, boolean refresh, boolean imports) {
        _redownloadSet = refresh ? new HashSet<>() : null;

        if (filenames.length > 0) {
            syncCacheWithLocalXsdFiles(filenames, true);
        } else if (sync) {
            syncCacheWithLocalXsdFiles(getAllXSDFilenames(), false);
        }

        Set starterset = new HashSet<>();

        for (String s : uris) {
            SchemaResource resource = (SchemaResource) lookupResource(null, s);
            if (resource != null) {
                starterset.add(resource);
            }
        }

        for (String filename : filenames) {
            SchemaResource resource = _resourceForFilename.get(filename);
            if (resource != null) {
                starterset.add(resource);
            }
        }

        SchemaResource[] starters = starterset.toArray(new SchemaResource[0]);

        if (refresh) {
            redownloadEntries(starters);
        }

        if (imports) {
            resolveImports(starters);
        }

        _redownloadSet = null;
    }

    /**
     * Adds items to the cache that point to new files that aren't
     * described in the cache, and optionally deletes old entries.
     * 

* If an old file is gone and a new file is * found with exactly the same contents, the cache entry is moved * to point to the new file. */ public final void syncCacheWithLocalXsdFiles(String[] filenames, boolean deleteOnlyMentioned) { Set seenResources = new HashSet<>(); Set vanishedResources = new HashSet<>(); for (String filename : filenames) { // first, if the filename matches exactly, trust the filename SchemaResource resource = _resourceForFilename.get(filename); if (resource != null) { if (fileExists(filename)) { seenResources.add(resource); } else { vanishedResources.add(resource); } continue; } // new file that is not in the index? // not if the digest is known to the index and the original file is gone - that's a rename! String digest = null; try { digest = shaDigestForFile(filename); resource = _resourceForDigest.get(digest); if (resource != null) { String oldFilename = resource.getFilename(); if (!fileExists(oldFilename)) { warning("File " + filename + " is a rename of " + oldFilename); resource.setFilename(filename); seenResources.add(resource); if (_resourceForFilename.get(oldFilename) == resource) { _resourceForFilename.remove(oldFilename); } if (_resourceForFilename.containsKey(filename)) { _resourceForFilename.put(filename, resource); } continue; } } } catch (IOException e) { // unable to read digest... no problem, ignore then } // ok, this really is a new XSD file then, of unknown URL origin DownloadedSchemaEntry newEntry = addNewEntry(); newEntry.setFilename(filename); warning("Caching information on new local file " + filename); if (digest != null) { newEntry.setSha1(digest); } seenResources.add(updateResource(newEntry)); } if (deleteOnlyMentioned) { deleteResourcesInSet(vanishedResources, true); } else { deleteResourcesInSet(seenResources, false); } } /** * Iterates through every entry and refetches it from its primary URL, * if known. Replaces the contents of the file if the data is different. */ private void redownloadEntries(SchemaResource[] resources) { for (SchemaResource resource : resources) { redownloadResource(resource); } } private void deleteResourcesInSet(Set seenResources, boolean setToDelete) { Set seenCacheEntries = new HashSet<>(); for (SchemaResource resource : seenResources) { seenCacheEntries.add(resource._cacheEntry); } DownloadedSchemas downloadedSchemas = _importsDoc.getDownloadedSchemas(); for (int i = 0; i < downloadedSchemas.sizeOfEntryArray(); i++) { DownloadedSchemaEntry cacheEntry = downloadedSchemas.getEntryArray(i); if (seenCacheEntries.contains(cacheEntry) == setToDelete) { SchemaResource resource = _resourceForCacheEntry.get(cacheEntry); if (resource != null) { warning("Removing obsolete cache entry for " + resource.getFilename()); _resourceForCacheEntry.remove(cacheEntry); if (resource == _resourceForFilename.get(resource.getFilename())) { _resourceForFilename.remove(resource.getFilename()); } if (resource == _resourceForDigest.get(resource.getSha1())) { _resourceForDigest.remove(resource.getSha1()); } if (resource == _resourceForNamespace.get(resource.getNamespace())) { _resourceForNamespace.remove(resource.getNamespace()); } // Finally, any or all URIs String[] urls = resource.getSchemaLocationArray(); for (String url : urls) { if (resource == _resourceForURL.get(url)) { _resourceForURL.remove(url); } } } downloadedSchemas.removeEntry(i); i -= 1; } } } private SchemaResource updateResource(DownloadedSchemaEntry entry) { // The file String filename = entry.getFilename(); if (filename == null) { return null; } SchemaResource resource = new SchemaResource(entry); _resourceForCacheEntry.put(entry, resource); if (!_resourceForFilename.containsKey(filename)) { _resourceForFilename.put(filename, resource); } // The digest String digest = resource.getSha1(); if (digest != null) { if (!_resourceForDigest.containsKey(digest)) { _resourceForDigest.put(digest, resource); } } // Next, the namespace String namespace = resource.getNamespace(); if (namespace != null) { if (!_resourceForNamespace.containsKey(namespace)) { _resourceForNamespace.put(namespace, resource); } } // Finally, any or all URIs String[] urls = resource.getSchemaLocationArray(); for (String url : urls) { if (!_resourceForURL.containsKey(url)) { _resourceForURL.put(url, resource); } } return resource; } private static DigestInputStream digestInputStream(InputStream input) { MessageDigest sha; try { sha = MessageDigest.getInstance("SHA"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); } return new DigestInputStream(input, sha); } private DownloadedSchemaEntry addNewEntry() { return _importsDoc.getDownloadedSchemas().addNewEntry(); } private class SchemaResource implements SchemaImportResolver.SchemaResource { SchemaResource(DownloadedSchemaEntry entry) { _cacheEntry = entry; } DownloadedSchemaEntry _cacheEntry; public void setFilename(String filename) { _cacheEntry.setFilename(filename); } public String getFilename() { return _cacheEntry.getFilename(); } public Schema getSchema() { if (!fileExists(getFilename())) { redownloadResource(this); } try { return SchemaDocument.Factory.parse(inputStreamForFile(getFilename())).getSchema(); } catch (Exception e) { return null; // return null if _any_ problems reading schema file } } public String getSha1() { return _cacheEntry.getSha1(); } public String getNamespace() { return _cacheEntry.getNamespace(); } public void setNamespace(String namespace) { _cacheEntry.setNamespace(namespace); } public String getSchemaLocation() { if (_cacheEntry.sizeOfSchemaLocationArray() > 0) { return _cacheEntry.getSchemaLocationArray(0); } return null; } public String[] getSchemaLocationArray() { return _cacheEntry.getSchemaLocationArray(); } public int hashCode() { return getFilename().hashCode(); } public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof SchemaResource)) { return false; } SchemaResource sr = (SchemaResource) obj; return getFilename().equals(sr.getFilename()); } public void addSchemaLocation(String schemaLocation) { _cacheEntry.addSchemaLocation(schemaLocation); } } /** * Called when the ImportLoader wishes to resolve the * given import. Should return a SchemaResource whose * "equals" relationship reveals when a SchemaResource is * duplicated and shouldn't be examined again. *

* Returns null if the resource reference should be ignored. */ public SchemaImportResolver.SchemaResource lookupResource(String nsURI, String schemaLocation) { SchemaResource result = fetchFromCache(nsURI, schemaLocation); if (result != null) { if (_redownloadSet != null) { redownloadResource(result); } return result; } if (schemaLocation == null) { warning("No cached schema for namespace '" + nsURI + "', and no url specified"); return null; } result = copyOrIdentifyDuplicateURL(schemaLocation, nsURI); if (_redownloadSet != null) { _redownloadSet.add(result); } return result; } private SchemaResource fetchFromCache(String nsURI, String schemaLocation) { SchemaResource result; if (schemaLocation != null) { result = _resourceForURL.get(schemaLocation); if (result != null) { return result; } } if (nsURI != null) { result = _resourceForNamespace.get(nsURI); if (result != null) { return result; } } return null; } private String uniqueFilenameForURI(String schemaLocation) throws IOException, URISyntaxException { String localFilename = new URI(schemaLocation).getRawPath(); int i = localFilename.lastIndexOf('/'); if (i >= 0) { localFilename = localFilename.substring(i + 1); } if (localFilename.endsWith(".xsd")) { localFilename = localFilename.substring(0, localFilename.length() - 4); } if (localFilename.length() == 0) { localFilename = "schema"; } // TODO: remove other unsafe characters for filenames? String candidateFilename = localFilename; int suffix = 1; while (suffix < 1000) { String candidate = _defaultCopyDirectory + "/" + candidateFilename + ".xsd"; if (!fileExists(candidate)) { return candidate; } suffix += 1; candidateFilename = localFilename + suffix; } throw new IOException("Problem with filename " + localFilename + ".xsd"); } private void redownloadResource(SchemaResource resource) { if (_redownloadSet != null) { if (_redownloadSet.contains(resource)) { return; } _redownloadSet.add(resource); } String filename = resource.getFilename(); String schemaLocation = resource.getSchemaLocation(); String digest; // nothing to do? if (schemaLocation == null || filename == null) { return; } ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try { URL url = new URL(schemaLocation); URLConnection conn = url.openConnection(); conn.addRequestProperty("User-Agent", USER_AGENT); conn.addRequestProperty("Accept", "application/xml, text/xml, */*"); DigestInputStream input = digestInputStream(conn.getInputStream()); IOUtil.copyCompletely(input, buffer); digest = HexBin.bytesToString(input.getMessageDigest().digest()); } catch (Exception e) { warning("Could not copy remote resource " + schemaLocation + ":" + e.getMessage()); return; } if (digest.equals(resource.getSha1()) && fileExists(filename)) { warning("Resource " + filename + " is unchanged from " + schemaLocation + "."); return; } try { InputStream source = new ByteArrayInputStream(buffer.toByteArray()); writeInputStreamToFile(source, filename); } catch (IOException e) { warning("Could not write to file " + filename + " for " + schemaLocation + ":" + e.getMessage()); return; } warning("Refreshed " + filename + " from " + schemaLocation); } private SchemaResource copyOrIdentifyDuplicateURL(String schemaLocation, String namespace) { String targetFilename; String digest; SchemaResource result; try { targetFilename = uniqueFilenameForURI(schemaLocation); } catch (URISyntaxException e) { warning("Invalid URI '" + schemaLocation + "':" + e.getMessage()); return null; } catch (IOException e) { warning("Could not create local file for " + schemaLocation + ":" + e.getMessage()); return null; } try { URL url = new URL(schemaLocation); DigestInputStream input = digestInputStream(url.openStream()); writeInputStreamToFile(input, targetFilename); digest = HexBin.bytesToString(input.getMessageDigest().digest()); } catch (Exception e) { warning("Could not copy remote resource " + schemaLocation + ":" + e.getMessage()); return null; } result = _resourceForDigest.get(digest); if (result != null) { deleteFile(targetFilename); result.addSchemaLocation(schemaLocation); if (!_resourceForURL.containsKey(schemaLocation)) { _resourceForURL.put(schemaLocation, result); } return result; } warning("Downloaded " + schemaLocation + " to " + targetFilename); DownloadedSchemaEntry newEntry = addNewEntry(); newEntry.setFilename(targetFilename); newEntry.setSha1(digest); if (namespace != null) { newEntry.setNamespace(namespace); } newEntry.addSchemaLocation(schemaLocation); return updateResource(newEntry); } /** * Updates actual namespace in the table. */ public void reportActualNamespace(SchemaImportResolver.SchemaResource rresource, String actualNamespace) { SchemaResource resource = (SchemaResource) rresource; String oldNamespace = resource.getNamespace(); if (oldNamespace != null && _resourceForNamespace.get(oldNamespace) == resource) { _resourceForNamespace.remove(oldNamespace); } if (!_resourceForNamespace.containsKey(actualNamespace)) { _resourceForNamespace.put(actualNamespace, resource); } resource.setNamespace(actualNamespace); } private String shaDigestForFile(String filename) throws IOException { DigestInputStream str = digestInputStream(inputStreamForFile(filename)); byte[] dummy = new byte[4096]; int i = 1; while (i > 0) { i = str.read(dummy); } str.close(); return HexBin.bytesToString(str.getMessageDigest().digest()); } // SOME METHODS TO OVERRIDE ============================ protected String getIndexFilename() { return "./xsdownload.xml"; } protected String getDefaultSchemaDir() { return "./schema"; } /** * Produces diagnostic messages such as "downloading X to file Y". */ abstract protected void warning(String msg); /** * Returns true if the given filename exists. The filenames * are of the form "/foo/bar/zee.xsd" and should be construed * as rooted at the root of the project. */ abstract protected boolean fileExists(String filename); /** * Gets the data in the given filename as an InputStream. */ abstract protected InputStream inputStreamForFile(String filename) throws IOException; /** * Writes an entire file in one step. An InputStream is passed and * copied to the file. */ abstract protected void writeInputStreamToFile(InputStream input, String filename) throws IOException; /** * Deletes a file. Sometimes immediately after writing a new file * we notice that it's exactly the same as an existing file and * we delete it. We never delete a file that was given to us * by the user. */ abstract protected void deleteFile(String filename); /** * Returns a list of all the XSD filesnames in the project. */ abstract protected String[] getAllXSDFilenames(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy