/******************************************************************************* * Copyright 2012 Pearson Education * * 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.semantictools.frame.api; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.FileEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.semantictools.context.renderer.model.ContextProperties; import org.semantictools.context.renderer.model.OntologyEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.Ontology; import com.hp.hpl.jena.rdf.model.ModelFactory; public class OntologyManager { private static final String PROPERTIES_FILENAME = "asset.properties"; private static final String URI = "uri"; private static final String DEFAULT = "default"; private static final String TURTLE_FORMAT = "text/turtle"; private static final String XML_FORMAT = "text/xml"; private static final Logger logger = LoggerFactory.getLogger(OntologyManager.class); private String ontologyServiceURI; private Map<String, OntologyEntity> uri2OntologyEntity = new HashMap<String, OntologyEntity>(); private List<String> uploadList = new ArrayList<String>(); private File localRepository; /** * Returns the URI to which ontology files should be uploaded. */ public String getOntologyServiceURI() { return ontologyServiceURI; } /** * Returns the root directory for the local repository */ public File getLocalRepository() { return localRepository; } /** * Sets the root directory for the local repository * @param localRepository */ public void setLocalRepository(File localRepository) { this.localRepository = localRepository; } public void publishToLocalRepository(List<ContextProperties> contextList) { if (localRepository == null) return; publishSchemasToLocalRepo(); publishJsonLdContextsToLocalRepo(contextList); } private void publishJsonLdContextsToLocalRepo(List<ContextProperties> contextList) { for (ContextProperties p : contextList) { File contextFile = p.getContextFile(); String uri = p.getContextURI(); if (contextFile == null || uri==null) continue; File repoDir = repoDir(uri); File targetFile = writeAssetPropertiesFile(repoDir, uri, LdContentType.JSON_LD_CONTEXT); try { copyFile(contextFile, targetFile); } catch (IOException e) { logger.error("Failed to copy file " + contextFile, e); } } } private void publishSchemasToLocalRepo() { for (OntologyEntity entity : uri2OntologyEntity.values()) { String uri = entity.getOntologyURI(); File repoDir = repoDir(uri); LdContentType format = contentType(entity); File targetFile = writeAssetPropertiesFile(repoDir, uri, format); try { copyFile(entity.getFile(), targetFile); } catch (IOException e) { logger.error("Failed to copy file " + entity.getFile().getName(), e); } } } private LdContentType contentType(OntologyEntity entity) { return TURTLE_FORMAT.equals(entity.getContentType()) ? LdContentType.TURTLE : XML_FORMAT.equals(entity.getContentType()) ? LdContentType.XSD : null; } private File writeAssetPropertiesFile(File repoDir, String uri, LdContentType contentType) { repoDir.mkdirs(); String format = contentType.name(); String fileName = contentType.repoFileName(); Properties properties = new Properties(); properties.setProperty(URI, uri); properties.setProperty(DEFAULT, format); properties.setProperty(format, fileName); FileWriter writer = null; try { File file = new File(repoDir, PROPERTIES_FILENAME); writer = new FileWriter(file); properties.store(writer, null); } catch (Throwable oops) { logger.error("Failed to save properties at " + repoDir, oops); } finally { safeClose(writer); } return new File(repoDir, fileName); } private void safeClose(FileWriter writer) { if (writer == null) return; try { writer.close(); } catch (Throwable oops) { logger.warn("failed to close writer", oops); } } private void copyFile(File sourceFile, File targetFile) throws IOException { InputStream input = new FileInputStream(sourceFile); File parent = targetFile.getParentFile(); parent.mkdirs(); FileOutputStream out = new FileOutputStream(targetFile); try { byte[] buffer = new byte[1024]; int len; while ( (len = input.read(buffer)) > 0) { out.write(buffer, 0, len); } } finally { out.close(); } input.close(); } private File repoDir(String assetURI) { try { URI uri = new URI(assetURI); String path = uri.getAuthority() + "/" + uri.getPath(); return new File(localRepository, path); } catch (Throwable oops) { return null; } } /** * Sets the URI to which ontology files should be uploaded. */ public void setOntologyServiceURI(String ontologyServiceURI) { this.ontologyServiceURI = ontologyServiceURI; } /** * Returns the list of URI values for ontologies whose Turtle or XSD files are to be uploaded * to the ontology service. */ public List<String> getUploadList() { return uploadList; } public void uploadJsonLdContextFiles(List<ContextProperties> list) { if (ontologyServiceURI == null) return; for (ContextProperties p : list) { File file = p.getContextFile(); if (file != null) { try { uploadFile(file, "application/ld+json"); } catch (Throwable e) { logger.warn("Failed to upload file", e); } } } } /** * Scan the specified directory for schemas, and upload them to the ontology service, * but only if they are included in the upload list. * * @param rdfDir The directory that should be scanned for schema files. * @return The number of files uploaded. * @throws SchemaParseException * @throws IOException */ public int upload() throws SchemaParseException, IOException { if (ontologyServiceURI == null || uploadList.isEmpty()) return 0; Collections.sort(uploadList); int count = 0; for (String ontologyURI : uploadList) { OntologyEntity entity = uri2OntologyEntity.get(ontologyURI); if (entity == null) { logger.warn("Cannot upload ontology because file not found: " + ontologyURI); continue; } try { uploadFile(entity.getFile(), entity.getContentType()); count++; } catch (Throwable oops) { logger.warn("Failed to upload " + entity.getFile(), oops); } } return count; } private void uploadFile(File file, String contentType) throws ClientProtocolException, IOException { System.out.println("Uploading... " + file); HttpClient client = new DefaultHttpClient(); HttpPost post = new HttpPost(ontologyServiceURI); FileEntity fileEntity = new FileEntity(file, contentType); post.setEntity(fileEntity); HttpResponse response = client.execute(post); int status = response.getStatusLine().getStatusCode(); switch (status) { case HttpStatus.SC_OK : case HttpStatus.SC_CREATED : break; default: System.out.println(" ERROR: " + status); } } public void scan(File file) throws SchemaParseException { if (localRepository==null && ontologyServiceURI==null) return; if (file.isDirectory()) { for (File child : file.listFiles()) { scan(child); } } else { String fileName = file.getName(); if (fileName.endsWith(".xsd")) { loadXsd(file); } else if (fileName.endsWith(".ttl")) { loadTurtle(file); } } } private void loadTurtle(File file) throws SchemaParseException { if (file.getName().endsWith("_binding.ttl")) { // For now, ignore binding files. return; } try { OntModel model = ModelFactory.createOntologyModel(); FileReader reader = new FileReader(file); model.read(reader, null, "TURTLE"); List<Ontology> list = model.listOntologies().toList(); if (list.isEmpty()) { logger.warn("Ignoring file because it contains no ontology declarations: " + file); } else if (list.size() == 1) { Ontology onto = list.get(0); String ontologyURI = onto.getURI(); OntologyEntity entity = new OntologyEntity(TURTLE_FORMAT, file, ontologyURI); uri2OntologyEntity.put(ontologyURI, entity); } else { logger.warn("Ignoring file because it contains more than one ontology: " + file); } } catch (Throwable oops) { throw new SchemaParseException(oops); } } private void loadXsd(File file) throws SchemaParseException { try { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); XMLReader reader = parser.getXMLReader(); reader.setFeature("http://xml.org/sax/features/namespaces", true); NamespaceReader handler = new NamespaceReader(); reader.setContentHandler(handler); parser.parse(file, handler); String namespace = handler.getTargetNamespace(); if (namespace == null) { logger.warn("Ignoring schema since targetNamespace is not declared: " + file.getPath()); } else { OntologyEntity entity = new OntologyEntity(XML_FORMAT, file, namespace); uri2OntologyEntity.put(namespace, entity); } } catch (Throwable oops) { throw new SchemaParseException(oops); } } private class NamespaceReader extends DefaultHandler { private String targetNamespace; @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if ("schema".equals(localName)) { targetNamespace = attributes.getValue("targetNamespace"); } } public String getTargetNamespace() { return targetNamespace; } } public enum LdContentType { XSD("xsd"), TURTLE("ttl"), JSON_LD_CONTEXT("json"), ENHANCED_CONTEXT("json", JSON_LD_CONTEXT), UNKNOWN("???"); private String extension; private LdContentType defaultType; private LdContentType(String extension) { this.extension = extension; } private LdContentType(String extension, LdContentType defaultType) { this.extension = extension; this.defaultType = defaultType; } /** * Returns the extension that should be used for assets * of this content type. */ public String getExtension() { return extension; } /** * Returns the content type that should be regarded as the * default format for assets of this type. * If this content type is the default, then the return value * is this LdContentType instance. */ public LdContentType getDefaultType() { return defaultType == null ? this : defaultType; } /** * Returns true if this content type is a default content type. */ public boolean isDefaultType() { return defaultType==null || defaultType==this; } public String repoFileName() { return name() + "." + extension; } public static LdContentType guessContentType(String fileName) { int dot = fileName.lastIndexOf('.'); if (dot < 0) { return UNKNOWN; } String suffix = fileName.substring(dot+1); if (XSD.getExtension().equals(suffix)) return XSD; if (TURTLE.getExtension().equals(suffix)) return TURTLE; if (JSON_LD_CONTEXT.getExtension().equals(suffix)) return JSON_LD_CONTEXT; return UNKNOWN; } } }