/******************************************************************************* * Copyright (c) 2008 Cambridge Semantics Incorporated. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Cambridge Semantics Incorporated - Initial Implementation *******************************************************************************/ package org.openanzo.binarystore.server; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.ProgressListener; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.lang.StringUtils; import org.eclipse.jetty.http.HttpContent; import org.eclipse.jetty.http.HttpHeaders; import org.json.JSONException; import org.json.JSONWriter; import org.openanzo.binarystore.BinaryStoreDictionary; import org.openanzo.client.AnzoClient; import org.openanzo.client.ClientGraph; import org.openanzo.client.IStatementChannel; import org.openanzo.client.pool.AnzoClientPool; import org.openanzo.client.pool.RestrictedAnzoClient; import org.openanzo.exceptions.AnzoException; import org.openanzo.exceptions.ExceptionConstants; import org.openanzo.exceptions.LogUtils; import org.openanzo.exceptions.Messages; import org.openanzo.ontologies.openanzo.NamedGraph; import org.openanzo.rdf.Constants; import org.openanzo.rdf.IAnzoGraph; import org.openanzo.rdf.INamedGraph; import org.openanzo.rdf.IStatementListener; import org.openanzo.rdf.Literal; import org.openanzo.rdf.RDFFormat; import org.openanzo.rdf.Resource; import org.openanzo.rdf.Statement; import org.openanzo.rdf.URI; import org.openanzo.rdf.Constants.GRAPHS; import org.openanzo.rdf.utils.ReadWriteUtils; import org.openanzo.rdf.utils.SerializationUtils; import org.openanzo.rdf.utils.StatementUtils; import org.openanzo.rdf.vocabulary.DC; import org.openanzo.rdf.vocabulary.RDF; import org.openanzo.services.BinaryStoreConstants; import org.openanzo.services.ITracker; import org.openanzo.servlet.ResourceServerServlet; import org.openanzo.servlet.ServletDictionary; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; /** * Binary Store Servlet * * @author Simon Martin ( <a href="mailto:simon@cambridgesemantics.com">simon@cambridgesemantics.com </a>) * */ public class BinaryStoreServlet extends ResourceServerServlet implements BinaryStoreConstants { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(BinaryStoreServlet.class); private String serverNodeRootPath = null; private String serverRootPath = null; private String servletPath = null; private String serverNode = null; private static Integer directoryIncrementer = 0; private static Boolean directoryIncrementerSet = false; private static Calendar currentCal = null; private DiskFileItemFactory factory = null; private static final int MAX_MEMORY_SIZE = 1048576; private long progressUpdateFrequency = 0; private LockFileUpdater lockFileUpdater = null; private String nodelockid = null; private File varDirFile = null; private String docRoot = null; private AnzoClientPool clientPool = null; private RestrictedAnzoClient anzoClient = null; BinaryStoreServlet(AnzoClientPool clientPool) { this.clientPool = clientPool; } @Override public String getServletInfo() { return "Binary Store Servlet"; } void initialize(Dictionary<?, ?> configProperties) throws AnzoException { this.serverNode = BinaryStoreDictionary.getServerNode(configProperties); this.serverRootPath = BinaryStoreDictionary.getFileSystemRoot(configProperties); this.docRoot = ServletDictionary.getDocRoot(configProperties); if (docRoot.startsWith("./")) { String root = System.getProperty("ANZO_HOME"); docRoot = root + docRoot.substring((root.endsWith("/") ? 2 : 1)); } // this.loginPageURL=URIUtil.addPaths(context, this.loginPageURL); // this.errorPageURL=URIUtil.addPaths(context, this.errorPageURL); this.progressUpdateFrequency = BinaryStoreDictionary.getProgressUpdateFrequency(configProperties, Long.valueOf(0)); File f = new File(serverRootPath); File serverNodeFile = new File(f, serverNode); if (!serverNodeFile.isDirectory()) { serverNodeFile.mkdirs(); } serverNodeRootPath = serverNodeFile.getAbsolutePath(); varDirFile = new File(f, BINARYSTORE_VAR_DIRECTORY); File varNodeDirFile = new File(varDirFile, serverNode); //remove any old uuid for this process. deleteDirectory(varNodeDirFile); varNodeDirFile.mkdirs(); String lockid = UUID.randomUUID().toString(); nodelockid = LOCKFILE_PREFIX + serverNode + LOCKFILE_DELIMETER + lockid; //create update thread to update process id in node var directory. try { lockFileUpdater = new LockFileUpdater(new File(varNodeDirFile, lockid).getAbsolutePath()); } catch (IOException e) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR, e); } lockFileUpdater.start(); try { serverNodeRootPath = serverNodeFile.getCanonicalPath(); } catch (IOException e) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR, e); } currentCal = Calendar.getInstance(); factory = new DiskFileItemFactory(); factory.setSizeThreshold(MAX_MEMORY_SIZE); setDirectoryIncrementer(); Thread initThread = new Thread() { @Override public void run() { try { anzoClient = clientPool.getAnzoClient(true, "BinaryStoreServlet"); anzoClient.getRealtimeUpdates().addTracker(null, RDF.TYPE, BINARYSTORE_ITEM_URI, null, new IStatementListener<ITracker>() { public void statementsRemoved(ITracker source, Statement... stmts) { for (Statement s : stmts) { deleteFile(s.getNamedGraphUri(), -1); } } public void statementsAdded(ITracker source, Statement... stmts) { } }); } catch (AnzoException ae) { log.error(LogUtils.SERVER_INTERNAL_MARKER, Messages.formatString(ExceptionConstants.COMBUS.JMS_REGISTER_SELECTOR_ERROR, " for Binary Store Servlet"), ae); } } }; initThread.start(); } void stop(boolean bundleStopping) throws AnzoException { lockFileUpdater.shutdown(); if (bundleStopping) { try { anzoClient.getRealtimeUpdates().removeTracker(null, RDF.TYPE, BINARYSTORE_ITEM_URI, null); } catch (AnzoException ae) { if (!bundleStopping) { log.error(LogUtils.SERVER_INTERNAL_MARKER, Messages.formatString(ExceptionConstants.COMBUS.JMS_UNREGISTER_SELECTOR_ERROR, " for Binary Store Servlet"), ae); } else { log.debug(LogUtils.SERVER_INTERNAL_MARKER, Messages.formatString(ExceptionConstants.COMBUS.JMS_UNREGISTER_SELECTOR_ERROR, " for Binary Store Servlet"), ae); } } } clientPool.returnAnzoClient(anzoClient, bundleStopping); } void reset() throws AnzoException { synchronized (directoryIncrementer) { File dir = new File(serverNodeRootPath); deleteDirectory(dir); dir.mkdirs(); directoryIncrementer = 0; } } private void setDirectoryIncrementer() { synchronized (directoryIncrementer) { if (!directoryIncrementerSet) { Calendar cal = Calendar.getInstance(); int year = cal.get(Calendar.YEAR); int month = (cal.get(Calendar.MONTH) + 1); int day = cal.get(Calendar.DAY_OF_MONTH); String fn = serverNodeRootPath + File.separator; fn += year + File.separator; fn += month + File.separator; fn += day + File.separator; File dir = new File(fn); int inc = -1; if (dir.isDirectory()) { File[] files = dir.listFiles(); for (File f : files) { try { int i = Integer.parseInt(f.getName()); if (i > inc) inc = i; } catch (NumberFormatException e) { } } } synchronized (directoryIncrementerSet) { if (inc != -1) directoryIncrementer = inc; directoryIncrementerSet = true; } } } } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { if (req.getUserPrincipal() == null) { resp.setHeader(AUTH_HEADER, String.valueOf(HttpServletResponse.SC_UNAUTHORIZED)); resp.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } if (servletPath == null) servletPath = req.getScheme() + "://" + req.getHeader("host") + req.getContextPath() + req.getServletPath(); String pathInfo = req.getPathInfo(); if (pathInfo.length() > 0 && pathInfo.startsWith("/")) pathInfo = pathInfo.substring(1); if (pathInfo.equals(NOOP)) { // Handle the NO-OP operation which is used to check proper authentication by clients before uploading a big file. // The 100-Continue HTTP dance unfortunately doesn't work too well due to poor support by clients and servers. So // this NOOP operation is an alternative. sendNOOPResponse(req, resp); return; } //a pool of anzoClients RestrictedAnzoClient ac = null; String user = null; try { try { ac = clientPool.getAnzoClient(true, "BinaryStoreOperation"); user = req.getUserPrincipal().getName(); RestrictedAnzoClient rac = ac; rac.setServiceUser(user); String runAsUser = req.getHeader(AUTHRUNAS_HEADER); if (runAsUser != null && runAsUser.length() > 0) { if (ac.getServicePrincipal().isSysadmin()) { rac.setServiceUser(runAsUser); } } } catch (AnzoException ae) { MDC.put(LogUtils.REMOTE_ADDRESS, req.getRemoteAddr()); log.error(LogUtils.BINARY_MARKER, Messages.formatString(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR_PROCESSING_REQUEST), ae); MDC.clear(); resp.setContentType(RDFFormat.JSON.getDefaultMIMEType()); resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); try { SerializationUtils.writeExceptionJSON(ae, resp.getWriter()); } catch (JSONException jsone) { log.debug(LogUtils.BINARY_MARKER, Messages.formatString(ExceptionConstants.IO.ERROR_SERIALIZING_JSON), jsone); } return; } if (pathInfo.equals(CREATE) || pathInfo.equals(UPDATE)) { try { createUpdate(ac, req, resp, pathInfo.equals(UPDATE)); } catch (AnzoException e) { MDC.put(LogUtils.REMOTE_ADDRESS, req.getRemoteAddr()); MDC.put(LogUtils.USER, user); log.info(LogUtils.BINARY_MARKER, Messages.formatString(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR_PROCESSING_REQUEST), e); sendJSONError(req, resp, e); MDC.clear(); return; } } else if (pathInfo.equals(READ)) { String uri = req.getParameter(GRAPH); if (uri == null) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } RequestDispatcher dispatcher = req.getRequestDispatcher(uri); if (dispatcher != null) { dispatcher.forward(req, resp); return; } } else if (pathInfo.equals(DELETE)) { try { delete(ac, req, resp); } catch (AnzoException e) { MDC.put(LogUtils.REMOTE_ADDRESS, req.getRemoteAddr()); MDC.put(LogUtils.USER, user); log.info(LogUtils.BINARY_MARKER, Messages.formatString(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR_PROCESSING_REQUEST), e); sendJSONError(req, resp, e); MDC.clear(); return; } } else { String uri = req.getRequestURL().toString(); int rc = HttpServletResponse.SC_NOT_FOUND; if (uri != null) { try { rc = read(ac, uri, req, resp); } catch (AnzoException e) { MDC.put(LogUtils.REMOTE_ADDRESS, req.getRemoteAddr()); MDC.put(LogUtils.USER, user); log.info(LogUtils.BINARY_MARKER, Messages.formatString(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR_PROCESSING_REQUEST), e); sendJSONError(req, resp, e); MDC.clear(); return; } } if (rc != HttpServletResponse.SC_OK) resp.sendError(rc); } } finally { if (ac != null) { clientPool.returnAnzoClient(ac, true); } } } catch (JSONException ae) { MDC.put(LogUtils.REMOTE_ADDRESS, req.getRemoteAddr()); log.error(LogUtils.BINARY_MARKER, Messages.formatString(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR_PROCESSING_REQUEST), ae); MDC.clear(); resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); resp.getWriter().write(ae.getMessage()); } } @SuppressWarnings( { "unchecked", "null" }) private void createUpdate(AnzoClient ac, HttpServletRequest req, HttpServletResponse resp, boolean update) throws ServletException, AnzoException, IOException, JSONException { //check permissions before uploading the file. URI updateURI = null; if (update) { String graphURI = req.getParameter(GRAPH); if (graphURI == null) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_NO_GRAPHURI_SPECIFIED); } updateURI = Constants.valueFactory.createURI(graphURI); if (!ac.namedGraphExists(updateURI)) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_UPDATE_FILEDOESNOTEXIST, updateURI.toString()); } else if (!ac.canAddToNamedGraph(updateURI) || (!ac.canRemoveFromNamedGraph(updateURI))) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_UPDATE_PERMISSION_DENIED, updateURI.toString()); } } else { if (!ac.canAddToNamedGraph(GRAPHS.GRAPHS_DATASET)) throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_UPDATE_PERMISSION_DENIED, GRAPHS.GRAPHS_DATASET.toString()); } String feedbackId = req.getParameter(FEEDBACK_ID); if (!ServletFileUpload.isMultipartContent(req)) throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_MULTIPART_FORM_REQUIRED); IStatementChannel scg = null; URI feedbackidURIg = null; ServletFileUpload upload = new ServletFileUpload(factory); if (feedbackId != null) { //Create a progress listener final URI feedbackURI = Constants.valueFactory.createURI(createFeedbackURI(ac)); final URI feedbackidURI = Constants.valueFactory.createURI(feedbackId); try { final IStatementChannel sc = ac.getStatementChannel(feedbackURI, AnzoClient.NON_REVISIONED_NAMED_GRAPH); scg = sc; feedbackidURIg = feedbackidURI; ac.updateRepository(); ProgressListener progressListener = new ProgressListener() { Date prevTime = null; public void update(long pBytesRead, long pContentLength, int pItems) { Date nowTime = new Date(); if ((pContentLength != pBytesRead) && (prevTime != null && (nowTime.getTime() < (prevTime.getTime() + progressUpdateFrequency)))) { return; } prevTime = nowTime; Map<String, Object> messageProperties = new HashMap<String, Object>(); Collection<Statement> statements = new HashSet<Statement>(); statements.add(Constants.valueFactory.createStatement(feedbackidURI, BINARYSTORE_ITEM_PROGRESS_JOB_URI, BINARYSTORE_ITEM_UPLOAD_JOB_URI, feedbackidURI)); statements.add(Constants.valueFactory.createStatement(feedbackidURI, BINARYSTORE_ITEM_PROGRESS_JOB_COMPLETE_URI, Constants.valueFactory.createLiteral(pContentLength), feedbackidURI)); statements.add(Constants.valueFactory.createStatement(feedbackidURI, BINARYSTORE_ITEM_PROGRESS_JOB_COMPLETED_URI, Constants.valueFactory.createLiteral(pBytesRead), feedbackidURI)); try { sc.sendMessage(messageProperties, statements); } catch (AnzoException e) { log.error(LogUtils.BINARY_MARKER, Messages.formatString(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR_SENDING_PROGRESS), e); } } }; upload.setProgressListener(progressListener); } catch (AnzoException e) { log.error(LogUtils.BINARY_MARKER, Messages.formatString(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR_FINDING_STATEMENT_CHANNEL), e); } } long revision = -1; BinaryStoreFile bsf = null; ClientGraph graph = null; File target = null; File lockFile = null; try { if (update) { graph = ac.getServerGraph(updateURI); revision = graph.getRevision(); if (!isRevisioned(graph)) revision = -1; //increase to the new revision; if (revision != -1) ++revision; bsf = getFileFromURI(updateURI, revision); target = new File(bsf.getFilename()); lockFile = getLock(target); if (lockFile == null) { graph.close(); throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_FILE_LOCKED, target.getAbsolutePath()); } } FileItem file = null; try { List<FileItem> items = upload.parseRequest(req); for (FileItem item : items) { if (!item.getFieldName().equals(FILENAME)) continue; file = item; break; } } catch (FileUploadException e) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_FILE_UPLOAD_ERROR, e); } catch (Exception e) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_FILE_UPLOAD_ERROR, e); } if (file == null || StringUtils.isEmpty(file.getName())) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_NO_FILE_SENT); } if (scg != null) { Map<String, Object> messageProperties = new HashMap<String, Object>(); Collection<Statement> statements = new HashSet<Statement>(); statements.add(Constants.valueFactory.createStatement(feedbackidURIg, BINARYSTORE_ITEM_PROGRESS_JOB_URI, BINARYSTORE_ITEM_CHECKSUM_JOB_URI, feedbackidURIg)); statements.add(Constants.valueFactory.createStatement(feedbackidURIg, BINARYSTORE_ITEM_PROGRESS_JOB_COMPLETE_URI, Constants.valueFactory.createLiteral(1), feedbackidURIg)); statements.add(Constants.valueFactory.createStatement(feedbackidURIg, BINARYSTORE_ITEM_PROGRESS_JOB_COMPLETED_URI, Constants.valueFactory.createLiteral(0), feedbackidURIg)); scg.sendMessage(messageProperties, statements); } String sha1sum = null; try { sha1sum = createChecksum(file.getInputStream()); } catch (IOException ioe) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR_CREATING_SHA1, file.getName()); } if (scg != null) { Map<String, Object> messageProperties = new HashMap<String, Object>(); Collection<Statement> statements = new HashSet<Statement>(); statements.add(Constants.valueFactory.createStatement(feedbackidURIg, BINARYSTORE_ITEM_PROGRESS_JOB_URI, BINARYSTORE_ITEM_CHECKSUM_JOB_URI, feedbackidURIg)); statements.add(Constants.valueFactory.createStatement(feedbackidURIg, BINARYSTORE_ITEM_PROGRESS_JOB_COMPLETE_URI, Constants.valueFactory.createLiteral(1), feedbackidURIg)); statements.add(Constants.valueFactory.createStatement(feedbackidURIg, BINARYSTORE_ITEM_PROGRESS_JOB_COMPLETED_URI, Constants.valueFactory.createLiteral(1), feedbackidURIg)); scg.sendMessage(messageProperties, statements); } String filename = new File(file.getName()).getName(); String contentType = file.getContentType(); long sizeInBytes = file.getSize(); if (update) { Collection<Statement> statements = graph.find(updateURI, BINARYSTORE_ITEM_SHA_1_URI, null); if (statements.size() > 0) { Statement s = statements.iterator().next(); String sum = ((Literal) s.getObject()).getLabel(); if (sum.equals(sha1sum)) { // no need to make a new revision as file is identical to currently stored file. if (revision != -1) --revision; bsf = getFileFromURI(updateURI, revision); sendSuccessMsg(req, resp, bsf.getURI(), revision); return; } } } else { revision = (req.getParameter(REVISIONED).equals("true")) ? 0 : -1; bsf = generateFilename(filename, revision); target = new File(bsf.getFilename()); //create the parent Directories File parentDir = target.getParentFile(); if (parentDir != null) parentDir.mkdirs(); } try { //its a new file if it is a create operation or the binary file is revisioned. if ((!update || (update && revision != -1)) && target.createNewFile() == false) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_FILE_ALREADY_EXISTS, target.getAbsolutePath()); } } catch (IOException ioe) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR_CREATING_FILE, target.getAbsolutePath()); } Resource fileURI = Constants.valueFactory.createResource(bsf.getURI().toString()); ac.begin(); if (!update) { if (revision == -1) { graph = ac.getServerGraph(bsf.getURI(), AnzoClient.NON_REVISIONED_NAMED_GRAPH); } else { graph = ac.getServerGraph(bsf.getURI(), AnzoClient.REVISIONED_NAMED_GRAPH); } graph.add(fileURI, RDF.TYPE, BINARYSTORE_ITEM_URI); } else { Collection<Statement> statements = graph.getStatements(); for (Statement s : statements) { if (s.getObject() != BINARYSTORE_ITEM_URI) { graph.remove(s); } } } //TODO: at this stage call out to any plugins which might want to add meta data to the graph. graph.add(fileURI, BINARYSTORE_ITEM_SIZE_URI, Constants.valueFactory.createLiteral(sizeInBytes)); graph.add(fileURI, CONTENT_TYPE_URI, Constants.valueFactory.createLiteral(contentType)); graph.add(fileURI, DC.TITLE, Constants.valueFactory.createLiteral(filename)); graph.add(fileURI, BINARYSTORE_ITEM_SHA_1_URI, Constants.valueFactory.createLiteral(sha1sum)); // copy the file to the correct place on the server. try { copy(file.getInputStream(), new FileOutputStream(target), scg); } catch (IOException ioe) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR_COPYING_FILE, file.getName(), target.getAbsolutePath()); } ac.commit(); ac.updateRepository(); } finally { if (graph != null) graph.close(); if (lockFile != null) lockFile.delete(); } if (scg != null) { scg.close(); try { //sleep so that the feedback events have enough time to send the final bytes complete event. Thread.sleep(200); } catch (InterruptedException e) { } } sendSuccessMsg(req, resp, bsf.getURI(), revision); } private String createFeedbackURI(AnzoClient ac) { String user = ac.getServiceUser(); return BINARYSTORE_ITEM_PROGRESS_CHANNEL_PREFIX + user; } private static boolean isRevisioned(ClientGraph graph) { INamedGraph metadata = graph.getMetadataGraph(); if (metadata.contains(graph.getNamedGraphUri(), NamedGraph.revisionedProperty, Constants.valueFactory.createLiteral(true))) { return true; } return false; } private int read(AnzoClient ac, String url, HttpServletRequest request, HttpServletResponse response) throws ServletException, AnzoException { IAnzoGraph graph = null; try { URI uri = Constants.valueFactory.createURI(url); if (!ac.namedGraphExists(uri)) { return HttpServletResponse.SC_NOT_FOUND; } if (!ac.canReadNamedGraph(uri)) { response.setHeader(AUTH_HEADER, String.valueOf(HttpServletResponse.SC_UNAUTHORIZED)); return HttpServletResponse.SC_UNAUTHORIZED; } String requested_revision_str = request.getParameter(URL_QUERY_REVISION); long requested_revision = -1; if (requested_revision_str != null) { requested_revision = Long.parseLong(requested_revision_str); } long revision = -1; if (requested_revision == -1) { ClientGraph g = ac.getServerGraph(uri); revision = g.getRevision(); if (!isRevisioned(g)) revision = -1; graph = g; } else { IAnzoGraph g = ac.getNamedGraphRevision(uri, requested_revision); if (g != null && g.size() > 0) // An empty graph (and with empty metadatagraph) is returned for revisions that don't exist revision = requested_revision; else throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_FILE_REVISION_DOES_NOT_EXIST, Long.toString(requested_revision), uri.toString()); graph = g; } Boolean showMetaData = false; if (request.getParameter(URL_QUERY_ASPECT) != null && request.getParameter(URL_QUERY_ASPECT).equals(URL_ASPECT_METADATA)) { showMetaData = true; } // if (showMetaData) { String formatString = request.getParameter(URL_QUERY_FORMAT); formatString = (formatString != null) ? formatString : RDF_XMLFORMAT; try { RDFFormat format = RDFFormat.forFileName("." + formatString); if (format == null) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_UNRECOGNIZEDRDFFORMAT, formatString); } StringWriter sw = new StringWriter(); ReadWriteUtils.writeGraph(graph, sw); response.setContentType(format.getDefaultMIMEType()); response.getOutputStream().print(sw.getBuffer().toString()); response.getOutputStream().flush(); return HttpServletResponse.SC_OK; } catch (Exception e) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_ERROR, e); } } BinaryStoreFile bsf = getFileFromURI(uri, revision); String filename = bsf.getFilename(); Collection<Statement> cos = graph.find(uri, CONTENT_TYPE_URI, null); if (cos.size() != 1) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_NO_CONTENTTYPE_FOUND, uri.toString()); } Statement s = cos.iterator().next(); String contentType = (String) StatementUtils.getNativeValue((Literal) s.getObject()); //TODO: at this stage run plugins to do transformations on the file. return serveStaticResource(request, response, filename, contentType); } finally { if (graph != null) graph.close(); } } private void delete(AnzoClient ac, HttpServletRequest req, HttpServletResponse resp) throws AnzoException, ServletException { String graphURI = req.getParameter(GRAPH); if (graphURI == null) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_NO_GRAPHURI_SPECIFIED); } URI uri = Constants.valueFactory.createURI(graphURI); if (!ac.namedGraphExists(uri)) throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_NO_GRAPHURI_SPECIFIED); if (!ac.canRemoveFromNamedGraph(uri)) throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_UPDATE_PERMISSION_DENIED, uri.toString()); ClientGraph graph = ac.getServerGraph(uri); if (!isRevisioned(graph)) { // if it is a non revisioned file then delete the file else just delete the graph if (!deleteFile(uri, -1)) throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_FILE_LOCKED, uri.toString()); } graph.getMetadataGraph().remove(uri, RDF.TYPE, NamedGraph.TYPE); ac.updateRepository(); } private boolean deleteFile(URI uri, long revision) { BinaryStoreFile bsf = getFileFromURI(uri, revision); File target = new File(bsf.getFilename()); File lockFile = getLock(target); if (lockFile == null) return false; if (target.exists()) { boolean success = target.delete(); lockFile.delete(); File parent = target; while (success && !parent.getParentFile().getAbsolutePath().equals(serverNodeRootPath) && parent.getParentFile().listFiles().length == 0) { parent = parent.getParentFile(); success = parent.delete(); } } return true; } private BinaryStoreFile generateFilename(String filename, long revision) { filename = removeIllegalURIChars(filename); Calendar cal = Calendar.getInstance(); int year = cal.get(Calendar.YEAR); int month = (cal.get(Calendar.MONTH) + 1); int day = cal.get(Calendar.DAY_OF_MONTH); int dirInt = 0; synchronized (directoryIncrementer) { if (day != currentCal.get(Calendar.DAY_OF_MONTH) && month != (currentCal.get(Calendar.MONTH) + 1) && year != currentCal.get(Calendar.YEAR)) { directoryIncrementer = 0; } currentCal = cal; dirInt = ++directoryIncrementer; } String fn = serverNodeRootPath + File.separator; fn += year + File.separator; fn += month + File.separator; fn += day + File.separator; fn += dirInt + File.separator; fn += filename; fn += (revision == -1) ? "" : "-0"; String url = servletPath + "/"; url += serverNode + "/"; url += year + "/"; url += month + "/"; url += day + "/"; url += dirInt + "/"; url += filename; return new BinaryStoreFile(fn, url); //eg "/binarystore/" + servernode + "/" + date_year + "/" + date_month + "/" + date_day + "/" + incremented_integer + "/" + filename } private BinaryStoreFile getFileFromURI(URI u, long revision) { String uri = u.toString(); if (!uri.startsWith(servletPath)) return null; String file = serverRootPath + uri.substring(servletPath.length()); file += (revision != -1) ? "-" + revision : ""; File f = new File(file); return new BinaryStoreFile(f.getAbsolutePath(), uri); } @SuppressWarnings("unchecked") private int serveStaticResource(HttpServletRequest request, HttpServletResponse response, String filename, String contentType) throws AnzoException, ServletException { // Find the resource and content HttpContent content = null; org.eclipse.jetty.util.resource.Resource resource = null; try { resource = org.eclipse.jetty.util.resource.Resource.newResource("file://" + filename); Enumeration reqRanges = null; // Is this a range request? reqRanges = request.getHeaders(HttpHeaders.RANGE); if (reqRanges != null && !reqRanges.hasMoreElements()) reqRanges = null; // Handle resource if (resource == null || !resource.exists()) return HttpServletResponse.SC_NOT_FOUND; if (!resource.isDirectory()) { // ensure we have content content = new UnCachedContent(resource, contentType); if (passConditionalHeaders(request, response, resource, content)) { sendData(request, response, false, resource, content, reqRanges); } } else { return HttpServletResponse.SC_NOT_FOUND; } } catch (IllegalArgumentException e) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_SENDING_DATA_FAILED, e, filename); } catch (IOException e) { throw new AnzoException(ExceptionConstants.BINARYSTORE.BINARYSTORE_SENDING_DATA_FAILED, e, filename); } finally { if (content != null) { content.release(); } if (resource != null) { resource.release(); } } return HttpServletResponse.SC_OK; } Properties getInitProperties() { Properties properties = new Properties(); @SuppressWarnings("unchecked") // javax.servlet.GenericServlet returns unchecked value Enumeration e = getInitParameterNames(); String paramName; String paramValue; while (e.hasMoreElements()) { paramName = (String) e.nextElement(); paramValue = getInitParameter(paramName); properties.put(paramName, paramValue); } return properties; } static private void sendNOOPResponse(HttpServletRequest req, HttpServletResponse resp) throws IOException { boolean addHTML = false; if ("application/json".equals(req.getHeader("Accept"))) { resp.setContentType("application/json"); } else { addHTML = true; resp.setContentType("text/html"); } resp.setStatus(HttpServletResponse.SC_OK); PrintWriter out = resp.getWriter(); String response = "{ \"error\": false }"; out.print(addHTML ? envelopeHTMLMessage(response) : response); out.flush(); out.close(); } static private void sendJSONError(HttpServletRequest req, HttpServletResponse resp, AnzoException exception) throws IOException, JSONException { boolean addHTML = false; if ("application/json".equals(req.getHeader("Accept"))) { resp.setContentType("application/json"); } else { addHTML = true; resp.setContentType("text/html"); } resp.setStatus(HttpServletResponse.SC_OK); PrintWriter out = resp.getWriter(); String jsonError = createJSONError(exception); out.print(addHTML ? envelopeHTMLMessage(jsonError) : jsonError); out.flush(); out.close(); } static private String createJSONError(AnzoException exception) throws JSONException { StringWriter sw = new StringWriter(); SerializationUtils.writeExceptionJSON(exception, sw); return sw.toString(); } static private void sendSuccessMsg(HttpServletRequest req, HttpServletResponse resp, URI uri, long revision) throws IOException, JSONException { boolean addHTML = false; if ("application/json".equals(req.getHeader("Accept"))) { resp.setContentType("application/json"); } else { addHTML = true; resp.setContentType("text/html"); } resp.setStatus(HttpServletResponse.SC_OK); PrintWriter out = resp.getWriter(); out.print(addHTML ? envelopeHTMLMessage(createReturnMessage(uri, revision)) : createReturnMessage(uri, revision)); out.flush(); out.close(); } static private String createReturnMessage(URI uri, long revision) throws JSONException { StringWriter sw = new StringWriter(); JSONWriter jj = new JSONWriter(sw); jj.object(); jj.key("error"); jj.value(false); jj.key("uri"); jj.value(uri.toString()); jj.key("revision"); jj.value(revision); jj.endObject(); return sw.toString(); } static private String envelopeHTMLMessage(String str) { return ("<html> <head></head><body> <textarea style='width: 100%; height: 100%;'>" + str + "</textarea></body></html>"); } static void copy(InputStream in, OutputStream out, IStatementChannel scg) throws IOException { // Do not allow other threads to read from the input // or write to the output while copying is taking place synchronized (in) { synchronized (out) { byte[] buffer = new byte[256]; while (true) { int bytesRead = in.read(buffer); if (bytesRead == -1) break; out.write(buffer, 0, bytesRead); } } } } static String removeIllegalURIChars(String s) { char[] chars = s.toCharArray(); StringBuilder b = new StringBuilder(); for (char c : chars) { if (c == '\'' || c == '"' || c == ' ') b.append("_"); else b.append(c); } return b.toString(); } private File getLock(File target) { File parent = target.getParentFile(); File lockfile = new File(parent, nodelockid); boolean exists = true; try { exists = lockfile.createNewFile(); } catch (IOException e) { return null; } if (!exists) { return null; } File[] files = parent.listFiles(); for (File f : files) { if (!f.equals(lockfile)) { if (f.getName().startsWith(LOCKFILE_PREFIX)) { String lock = f.getName().substring(LOCKFILE_PREFIX.length()); String[] nodeid = lock.split(LOCKFILE_DELIMETER); if (nodeid.length != 2) continue; boolean validLock = isLockValid(nodeid[0], nodeid[1]); if (!validLock) { //its an invalid lockfile - presumably created by a crashed or shutdown server so lets clean it up. f.delete(); } else { lockfile.delete(); return null; } } } } return lockfile; } private boolean isLockValid(String node, String id) { File nodeDir = new File(varDirFile, node); if (nodeDir.exists()) { File idFile = new File(nodeDir, id); if (idFile.exists()) { Date d = new Date(); if (d.getTime() < idFile.lastModified() + BINARYSTORE_HEARTBEAT_CHECKTIME) { return true; } else { //clean up the file as it is an old one. idFile.delete(); } } } return false; } static String createChecksum(InputStream fis) throws IOException { byte[] buffer = new byte[1024]; MessageDigest complete = null; try { complete = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { log.warn(LogUtils.SERVER_INTERNAL_MARKER, Messages.formatString(ExceptionConstants.BINARYSTORE.BINARYSTORE_NO_SHA1)); return null; } int numRead; do { numRead = fis.read(buffer); if (numRead > 0) { complete.update(buffer, 0, numRead); } } while (numRead != -1); fis.close(); byte[] digest = complete.digest(); String hash = ""; for (int i = 0; i < digest.length; i++) { String hex = Integer.toHexString(digest[i]); if (hex.length() == 1) hex = "0" + hex; hex = hex.substring(hex.length() - 2); hash += hex; } return hash; } private void deleteDirectory(File file) { if (file.isDirectory()) { for (File subFile : file.listFiles()) { deleteDirectory(subFile); } } file.delete(); } class BinaryStoreFile { private String _filename = null; private URI _uri = null; public BinaryStoreFile(String filename, String url) { _filename = filename; _uri = Constants.valueFactory.createURI(url); } public String getFilename() { return _filename; } public URI getURI() { return _uri; } } }