Index: oak-http/src/main/java/org/apache/jackrabbit/oak/http/HtmlRepresentation.java =================================================================== --- oak-http/src/main/java/org/apache/jackrabbit/oak/http/HtmlRepresentation.java (revision 1855537) +++ oak-http/src/main/java/org/apache/jackrabbit/oak/http/HtmlRepresentation.java (working copy) @@ -17,6 +17,8 @@ package org.apache.jackrabbit.oak.http; import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import javax.servlet.http.HttpServletResponse; @@ -35,10 +37,13 @@ import org.apache.tika.sax.XHTMLContentHandler; import org.xml.sax.SAXException; +import org.apache.commons.io.IOUtils; + import com.google.common.base.Charsets; import static org.apache.jackrabbit.oak.api.Type.STRING; import static org.apache.jackrabbit.oak.api.Type.STRINGS; +import static org.apache.jackrabbit.oak.api.Type.BINARY; class HtmlRepresentation implements Representation { @@ -65,17 +70,29 @@ xhtml.endElement("ol"); xhtml.endElement("dd"); } else { - xhtml.element("dd", property.getValue(STRING)); + if(BINARY.equals(property.getType())) { + String name = property.getName(); + String path = tree.getPath() + "/" + name; + xhtml.startElement("p"); + xhtml.startElement("a", "href", response.encodeRedirectURL( + escapePath(path))); + xhtml.characters(path); + xhtml.endElement("a"); + xhtml.endElement("p"); + } else { + xhtml.element("p", property.getValue(STRING)); + } } } for (Tree child : tree.getChildren()) { String name = child.getName(); + String path = child.getPath(); xhtml.element("dt", name); xhtml.startElement("dd"); xhtml.startElement("a", "href", response.encodeRedirectURL( - URLEncoder.encode(name, Charsets.UTF_8.name()) + "/")); - xhtml.characters(child.getPath()); + escapePath(path))); + xhtml.characters(path); xhtml.endElement("a"); xhtml.endElement("dd"); } @@ -90,21 +107,34 @@ public void render(PropertyState property, HttpServletResponse response) throws IOException { try { - XHTMLContentHandler xhtml = - startResponse(response, property.getName()); - xhtml.startDocument(); - - if (property.isArray()) { - xhtml.startElement("ol"); - for (String value : property.getValue(STRINGS)) { - xhtml.element("li", value); + if(BINARY.equals(property.getType())) { + response.setContentType("application/octet-stream"); + + InputStream in = null; + try { + in = property.getValue(BINARY).getNewStream(); + IOUtils.copy(in, response.getOutputStream()); + } finally { + IOUtils.closeQuietly(in); } - xhtml.endElement("ol"); - } else { - xhtml.element("p", property.getValue(STRING)); + + } else { + XHTMLContentHandler xhtml = + startResponse(response, property.getName()); + xhtml.startDocument(); + + if (property.isArray()) { + xhtml.startElement("ol"); + for (String value : property.getValue(STRINGS)) { + xhtml.element("li", value); + } + xhtml.endElement("ol"); + } else { + xhtml.element("p", property.getValue(STRING)); + } + + xhtml.endDocument(); } - - xhtml.endDocument(); } catch (SAXException e) { throw new IOException(e); } @@ -132,5 +162,17 @@ throw new IOException(e); } } + + private String escapePath(String path) throws UnsupportedEncodingException { + String pathParts[] = path.split("/"); + StringBuilder sb = new StringBuilder(); + + for(String part : pathParts) { + if(part.length()>0) + sb.append('/').append(URLEncoder.encode(part, Charsets.UTF_8.name())); + } + + return sb.toString(); + } } Index: oak-http/src/main/java/org/apache/jackrabbit/oak/http/OakServlet.java =================================================================== --- oak-http/src/main/java/org/apache/jackrabbit/oak/http/OakServlet.java (revision 1855537) +++ oak-http/src/main/java/org/apache/jackrabbit/oak/http/OakServlet.java (working copy) @@ -17,6 +17,7 @@ package org.apache.jackrabbit.oak.http; import java.io.IOException; +import java.io.InputStream; import java.util.Iterator; import java.util.Map.Entry; @@ -33,9 +34,12 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.smile.SmileFactory; + +import org.apache.commons.io.IOUtils; import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.ContentRepository; import org.apache.jackrabbit.oak.api.ContentSession; +import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.commons.PathUtils; @@ -42,6 +46,9 @@ import org.apache.jackrabbit.util.Base64; import org.apache.tika.mime.MediaType; +import static org.apache.jackrabbit.oak.api.Type.BINARY; +import static org.apache.jackrabbit.oak.api.Type.STRING; + public class OakServlet extends HttpServlet { private static final MediaType JSON = @@ -127,9 +134,37 @@ Tree tree = (Tree) request.getAttribute("tree"); representation.render(tree, response); } else { - // There was an extra path component that didn't match - // any existing nodes, so for now we just send a 404 response. - response.sendError(HttpServletResponse.SC_NOT_FOUND); + Tree tree = (Tree) request.getAttribute("tree"); + String name = path; + if(name.startsWith("/")) { + name = name.substring(1); + } + + if(tree.hasProperty(name)) { + + PropertyState property = tree.getProperty(name); + if(BINARY.equals(property.getType()) && tree.hasProperty("jcr:mimeType")) { + // Binary property with a known mime type is requested. + // Just write it back to response + String mime = tree.getProperty("jcr:mimeType").getValue(STRING); + response.setContentType(mime); + InputStream in = null; + try { + in = property.getValue(BINARY).getNewStream(); + IOUtils.copy(in, response.getOutputStream()); + } finally { + IOUtils.closeQuietly(in); + } + + } else { + representation.render(property, response); + } + + } else { + // There was an extra path component that didn't match + // any existing nodes or properties, so we just send a 404 response. + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } } } Index: oak-run/README.md =================================================================== --- oak-run/README.md (revision 1855537) +++ oak-run/README.md (working copy) @@ -353,6 +353,8 @@ --db - MongoDB database (default is a generated name) --clusterIds - Cluster Ids for the Mongo setup: a comma separated list of integers --base - Tar: Path to the base file + --fds-path - Tar-DS: Path to existing DataStore directory + --repository - Tar-DS: Path to existing segmentstore --mmap <64bit?> - TarMK memory mapping (the default on 64 bit JVMs) --rdbjdbcuri - JDBC URL for RDB persistence --rdbjdbcuser - JDBC username (defaults to "") Index: oak-run/src/main/java/org/apache/jackrabbit/oak/run/ServerCommand.java =================================================================== --- oak-run/src/main/java/org/apache/jackrabbit/oak/run/ServerCommand.java (revision 1855537) +++ oak-run/src/main/java/org/apache/jackrabbit/oak/run/ServerCommand.java (working copy) @@ -60,9 +60,12 @@ OptionParser parser = new OptionParser(); OptionSpec cache = parser.accepts("cache", "cache size (MB)").withRequiredArg().ofType(Integer.class).defaultsTo(100); + OptionSpec dsCache = parser.accepts("dsCache", "DataStore cache size (MB)").withRequiredArg().ofType(Integer.class).defaultsTo(100); // tar/h2 specific option OptionSpec base = parser.accepts("base", "Base directory").withRequiredArg().ofType(File.class); + OptionSpec repository = parser.accepts("repository", "Repository directory").withRequiredArg().ofType(File.class); + OptionSpec fds = parser.accepts("fds-path", "File DataStore directory").withRequiredArg().ofType(File.class); OptionSpec mmap = parser.accepts("mmap", "TarMK memory mapping").withOptionalArg().ofType(Boolean.class).defaultsTo("64".equals(System.getProperty("sun.arch.data.model"))); // mongo specific options: @@ -94,6 +97,7 @@ String fix = (arglist.size() <= 1) ? OakFixture.OAK_MEMORY : arglist.get(1); int cacheSize = cache.value(options); + int dsCacheSize = dsCache.value(options); List cIds = Collections.emptyList(); if (fix.startsWith(OakFixture.OAK_MEMORY)) { if (OakFixture.OAK_MEMORY_NS.equals(fix)) { @@ -123,6 +127,19 @@ throw new IllegalArgumentException("Required argument base missing."); } oakFixture = OakFixture.getVanillaSegmentTar(baseFile, 256, cacheSize, mmap.value(options)); + } else if (fix.equals(OakFixture.OAK_SEGMENT_TAR_DS)) { + File baseFile = base.value(options); + if (baseFile == null) { + throw new IllegalArgumentException("Required argument base missing."); + } + + File repoDir = repository.value(options); + File fdsDir = fds.value(options); + if(repoDir != null || fdsDir != null) { + oakFixture = OakFixture.getSegmentTarWithDataStore(baseFile, repoDir, fdsDir, 256, cacheSize, mmap.value(options), dsCacheSize); + } else { + oakFixture = OakFixture.getSegmentTarWithDataStore(baseFile, 256, cacheSize, mmap.value(options), dsCacheSize); + } } else if (fix.equals(OakFixture.OAK_RDB)) { oakFixture = OakFixture.getRDB(OakFixture.OAK_RDB, rdbjdbcuri.value(options), rdbjdbcuser.value(options), rdbjdbcpasswd.value(options), rdbjdbctableprefix.value(options), false, cacheSize, -1); Index: oak-run-commons/src/main/java/org/apache/jackrabbit/oak/fixture/OakFixture.java =================================================================== --- oak-run-commons/src/main/java/org/apache/jackrabbit/oak/fixture/OakFixture.java (revision 1855537) +++ oak-run-commons/src/main/java/org/apache/jackrabbit/oak/fixture/OakFixture.java (working copy) @@ -328,7 +328,21 @@ return new SegmentTarFixture(builder, withColdStandby, syncInterval, shareBlobStore, secure, oneShotRun); } + + public static OakFixture getSegmentTar(final String name, final File base, final File repository, final File fds, final int maxFileSizeMB, + final int cacheSizeMB, final boolean memoryMapping, final boolean useBlobStore, final int dsCacheInMB, + final boolean withColdStandby, final int syncInterval, final boolean shareBlobStore, final boolean secure, + final boolean oneShotRun) { + SegmentTarFixtureBuilder builder = SegmentTarFixtureBuilder.segmentTarFixtureBuilder(name, base); + builder.withMaxFileSize(maxFileSizeMB).withSegmentCacheSize(cacheSizeMB).withMemoryMapping(memoryMapping) + .withBlobStore(useBlobStore).withDSCacheSize(dsCacheInMB); + + builder.withRepository(repository).withFileDataStore(fds); + + return new SegmentTarFixture(builder, withColdStandby, syncInterval, shareBlobStore, secure, oneShotRun); + } + public static OakFixture getVanillaSegmentTar(final File base, final int maxFileSizeMB, final int cacheSizeMB, final boolean memoryMapping) { @@ -336,7 +350,7 @@ false, -1, false, false, false); } - public static OakFixture getSegmentTarWithDataStore(final File base, + public static OakFixture getSegmentTarWithDataStore(final File base, final int maxFileSizeMB, final int cacheSizeMB, final boolean memoryMapping, final int dsCacheInMB) { return getSegmentTar(OakFixture.OAK_SEGMENT_TAR_DS, base, maxFileSizeMB, cacheSizeMB, memoryMapping, true, dsCacheInMB, @@ -343,6 +357,13 @@ false, -1, false, false, false); } + public static OakFixture getSegmentTarWithDataStore(final File base, final File repository, final File fds, + final int maxFileSizeMB, final int cacheSizeMB, final boolean memoryMapping, final int dsCacheInMB) { + + return getSegmentTar(OakFixture.OAK_SEGMENT_TAR_DS, base, repository, fds, maxFileSizeMB, cacheSizeMB, + memoryMapping, true, dsCacheInMB, false, -1, false, false, false); + } + public static OakFixture getSegmentTarWithColdStandby(final File base, final int maxFileSizeMB, final int cacheSizeMB, final boolean memoryMapping, final boolean useBlobStore, final int dsCacheInMB, final int syncInterval, final boolean shareBlobStore, final boolean secure, final boolean oneShotRun) { Index: oak-run-commons/src/main/java/org/apache/jackrabbit/oak/fixture/SegmentTarFixture.java =================================================================== --- oak-run-commons/src/main/java/org/apache/jackrabbit/oak/fixture/SegmentTarFixture.java (revision 1855537) +++ oak-run-commons/src/main/java/org/apache/jackrabbit/oak/fixture/SegmentTarFixture.java (working copy) @@ -81,6 +81,9 @@ private String azureContainerName; private String azureRootPath; + private File fileDataStore; + private File repository; + public static SegmentTarFixtureBuilder segmentTarFixtureBuilder(String name, File directory) { return new SegmentTarFixtureBuilder(name, directory); } @@ -114,6 +117,16 @@ this.dsCacheSize = dsCacheSize; return this; } + + public SegmentTarFixtureBuilder withFileDataStore(File fds) { + this.fileDataStore = fds; + return this; + } + + public SegmentTarFixtureBuilder withRepository(File repository) { + this.repository = repository; + return this; + } public SegmentTarFixtureBuilder withAzure(String azureConnectionString, String azureContainerName, String azureRootPath) { this.azureConnectionString = azureConnectionString; @@ -145,6 +158,9 @@ private final String azureRootPath; private final File parentPath; + + private File fileDataStore; + private File repository; private FileStore[] stores; private BlobStoreFixture[] blobStoreFixtures; @@ -167,6 +183,8 @@ boolean shareBlobStore, boolean oneShotRun, boolean secure) { super(builder.name); this.base = builder.base; + this.repository = builder.repository; + this.fileDataStore = builder.fileDataStore; this.parentPath = new File(base, unique); this.maxFileSize = builder.maxFileSize; @@ -187,7 +205,10 @@ @Override public Oak getOak(int clusterId) throws Exception { - FileStoreBuilder fileStoreBuilder = fileStoreBuilder(parentPath) + File repo = repository == null ? parentPath : repository; + String fdsPath = fileDataStore == null ? parentPath.getAbsolutePath() : fileDataStore.getAbsolutePath(); + + FileStoreBuilder fileStoreBuilder = fileStoreBuilder(repo) .withMaxFileSize(maxFileSize) .withSegmentCacheSize(segmentCacheSize) .withMemoryMapping(memoryMapping); @@ -203,7 +224,13 @@ if (useBlobStore) { FileDataStore fds = new FileDataStore(); fds.setMinRecordLength(4092); - fds.init(parentPath.getAbsolutePath()); + + if(fdsPath.endsWith("/repository/datastore")) { + // In case a path to existing datastore is specified this bit is added by init method. + fdsPath = fdsPath.substring(0, fdsPath.length() - "/repository/datastore".length()); + } + + fds.init(fdsPath); BlobStore blobStore = new DataStoreBlobStore(fds); fileStoreBuilder.withBlobStore(blobStore);