package no.met.metadataeditor.datastore; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import no.met.metadataeditor.EditorException; import no.met.metadataeditor.validation.SchemaValidator; import no.met.metadataeditor.validation.Validator; import no.met.metadataeditor.validationclient.ValidationClient; import org.apache.commons.io.FilenameUtils; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; abstract class DataStoreImpl implements DataStore { private static final String SETUPFILE = "setup.xml"; private static final String CONFIGDIR = "config"; private static final String XMLDIR = "XML"; private static final String RESOURCE_DIR = "resources"; private Document setupDoc = null; private Date setupDocDate = null; private final Map<String, Validator> validator = new HashMap<>(); private final Map<String, Date> lastDataStoreDate = new HashMap<>(); private Document getSetupDoc() { String path = makePath(CONFIGDIR, SETUPFILE); // use existing if not too old if (setupDoc != null && setupDocDate != null) { Date lastModified = getLastModified(path); if (!lastModified.after(setupDocDate)) { return setupDoc; } } // fetch new file setupDocDate = getLastModified(path); String setupDocStr = get(path); javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance(); try { javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder(); setupDoc = db.parse(new InputSource(new java.io.StringReader(setupDocStr))); } catch (SAXException e) { Logger.getLogger(DataStoreImpl.class.getName()).log(Level.SEVERE, null, e); } catch (IOException e) { Logger.getLogger(DataStoreImpl.class.getName()).log(Level.SEVERE, null, e); } catch (ParserConfigurationException e) { Logger.getLogger(DataStoreImpl.class.getName()).log(Level.SEVERE, null, e); } return setupDoc; } /** * put a resource to the datastor * @param id * @param resource * @param username The username in the data store for the user doing the put action * @param password The password of the user doing the put action */ abstract void put(String id, String resource, String username, String password); /** * fetch a resource as string from the datastore * @param id * @return */ abstract String get(String id); /** * check the last change of a resource * @param id * @return date of last modification, or current date if not detectable */ abstract java.util.Date getLastModified(String id); /** * check if a resource exists * @param id * @return */ abstract boolean exists(String id); /** * build the full path for a datastore, for using * within the DataStore (i.e. for File, this is without prefix) * @param paths * @return */ String makePath(String... paths) { return makeURL(paths).toString(); } /** * build the full path for a datastore, including protocol-prefix * @param paths * @return */ abstract URL makeURL(String... paths); /** * @return A list of all available metadata in the data store */ abstract List<String> list(String url); /** * Delete a metadata record from the data store * @param url The record to delete. * @param username The username in the data store for the user doing the delete. * @param password The password of the user doing the delete. * @return True if the record was delete. False otherwise. */ abstract boolean delete(String url, String username, String password); @Override public boolean writeMetadata(String recordIdentifier, String xml, String username, String password) { String url = metadataUrl(recordIdentifier); put(url, xml, username, password); return true; } @Override public boolean metadataExists(String recordIdentifier) { return exists(metadataUrl(recordIdentifier)); } @Override public String readMetadata(String recordIdentifier) { return get(metadataUrl(recordIdentifier)); } @Override public String readTemplate(String recordIdentifier) { String metadata = readMetadata(recordIdentifier); SupportedFormat format = DataStoreUtils.getFormat(getSupportedFormats(), metadata); String templateUrl = templateUrl(format); if (!exists(templateUrl)) { throw new EditorException("Template does not exist: " + templateUrl, EditorException.MISSING_TEMPLATE); } return get(templateUrl); } @Override public String readTemplate(SupportedFormat format){ String templateUrl = templateUrl(format); List<SupportedFormat> formats = getSupportedFormats(); if( !formats.contains(format) ){ throw new EditorException("Format not supported by project: " + format, EditorException.UNSUPPORTED_FORMAT); } if (!exists(templateUrl)) { throw new EditorException("Template does not exist: " + templateUrl, EditorException.MISSING_TEMPLATE); } return get(templateUrl); } @Override public String readEditorConfiguration(String recordIdentifier) { String metadata = readMetadata(recordIdentifier); SupportedFormat format = DataStoreUtils.getFormat(getSupportedFormats(), metadata); String configurationUrl = editorConfigUrl(format); if (!exists(configurationUrl)) { throw new EditorException("Editor configuration does not exist: " + configurationUrl, EditorException.MISSING_EDITOR_CONFIG); } return get(configurationUrl); } @Override public String readResource(String resourceIdentifier) { String resourceUrl = resourceUrl(resourceIdentifier); if (!exists(resourceUrl)) { throw new EditorException("Resource does not exist: " + resourceUrl, EditorException.MISSING_METADATA_RESOURCE); } return get(resourceUrl); } private String templateUrl(SupportedFormat format) { return makePath(CONFIGDIR, format.templateName()); } private String editorConfigUrl(SupportedFormat format){ return makePath(CONFIGDIR, format.editorConfigName()); } private String resourceUrl(String resourceIdentifier){ return makePath(RESOURCE_DIR, resourceIdentifier); } private String metadataUrl(String recordIdentifier) { return makePath(XMLDIR, recordIdentifier + ".xml"); } protected String metadataDirUrl() { return makePath(XMLDIR); } @Override public List<SupportedFormat> getSupportedFormats() { Document doc = getSetupDoc(); return DataStoreUtils.parseSupportedFormats(doc); } @Override public List<String> availableMetadata(){ List<String> filenames = list(metadataDirUrl()); List<String> identifiers = new ArrayList<>(); for( String filename : filenames ){ identifiers.add(FilenameUtils.removeExtension(filename)); } // we sort the identifiers for simple automatic testing. Collections.sort(identifiers); return identifiers; } @Override public boolean deleteMetadata(String recordIdentifier, String username, String password){ return delete(metadataUrl(recordIdentifier), username, password); } @Override public Validator getValidator(String tag) throws IllegalArgumentException { synchronized (validator) { Document doc = getSetupDoc(); try { XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); XPathExpression expr = xpath.compile(String.format( "//internalValidators/validator[@tag='%s' and @type='simplePutService']", tag)); NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); if (nodes.getLength() != 1) { throw new IllegalArgumentException(String.format( "No unique internalValidator with tag %s in %s/%s", tag, CONFIGDIR, SETUPFILE)); } String argName = xpath.evaluate("arg/@name", nodes.item(0)); String argVal = xpath.evaluate("arg/@value", nodes.item(0)); String argId = String.format("%s:%s", argName, argVal); if ("resourceSchemaLocation".equals(argName)) { if (!validator.containsKey(argId)) { SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Logger.getLogger(DataStoreImpl.class.getName()).info(String.format("fetching schema for %s as %s from %s", tag, argVal, getClass().getResource(argVal))); Schema schema = sf.newSchema(getClass().getResource(argVal)); validator.put(argId, new SchemaValidator(schema)); } } else if ("diskSchemaLocation".equals(argName)) { //String argFilePath = xpath.evaluate("arg/@filePath", nodes.item(0)); if (!validator.containsKey(argId)) { SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Logger.getLogger(DataStoreImpl.class.getName()).info(String.format("fetching schema for %s as %s from %s", tag, argVal, argVal)); Schema schema = sf.newSchema(new File(argVal)); validator.put(argId, new SchemaValidator(schema)); } } else if ("externalSchemaLocation".equals(argName)) { if (!validator.containsKey(argId)) { SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Logger.getLogger(DataStoreImpl.class.getName()).info(String.format("fetching schema for %s as %s", tag, argVal)); Schema schema = sf.newSchema(new URL(argVal)); validator.put(argId, new SchemaValidator(schema)); } } else if ("webdavSchemaLocation".equals(argName)) { String path = makePath(CONFIGDIR, argVal); // remove validator if too old Date date = getLastModified(path); if (lastDataStoreDate.containsKey(path)) { if (lastDataStoreDate.get(path).before(date)) { validator.remove(argId); Logger.getLogger(DataStoreImpl.class.getName()).info(String.format("schema for %s as %s updated, was %s, new is %s", tag, argVal, lastDataStoreDate.get(path).toString(), date.toString())); } } if (!validator.containsKey(argId)) { SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Logger.getLogger(DataStoreImpl.class.getName()).info(String.format("fetching schema for %s from %s", tag, path)); Schema schema = sf.newSchema(makeURL(CONFIGDIR, argVal)); validator.put(argId, new SchemaValidator(schema)); lastDataStoreDate.put(path, date); } } else { throw new IllegalArgumentException(String.format( "Unknown argument name for validator with tag = %s", tag)); } return validator.get(argId); } catch (IOException e) { throw new IllegalArgumentException(e); } catch (SAXException e) { throw new IllegalArgumentException(e); } catch (XPathExpressionException e) { Logger.getLogger(DataStoreImpl.class.getName()).log(Level.SEVERE, null, e); throw new IllegalArgumentException(String.format("Internal error on handling tag = %s", tag)); } } } @Override public ValidationClient getValidationClient(String recordMetadata){ SupportedFormat format = DataStoreUtils.getFormat(getSupportedFormats(), recordMetadata); return format.getValidationClient(); } }