| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.jackrabbit.oak.run; |
| |
| import static java.util.Arrays.asList; |
| import static org.apache.jackrabbit.oak.commons.PropertiesUtil.populate; |
| import static org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentNodeStoreBuilder.newMongoDocumentNodeStoreBuilder; |
| import static org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentNodeStoreBuilder.newRDBDocumentNodeStoreBuilder; |
| import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder; |
| |
| import java.io.Closeable; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| |
| import javax.jcr.RepositoryException; |
| import javax.sql.DataSource; |
| |
| import joptsimple.OptionSpecBuilder; |
| import org.apache.commons.io.FileUtils; |
| import org.apache.felix.cm.file.ConfigurationHandler; |
| import org.apache.jackrabbit.core.data.DataStore; |
| import org.apache.jackrabbit.core.data.DataStoreException; |
| import org.apache.jackrabbit.oak.blob.cloud.azure.blobstorage.AzureDataStore; |
| import org.apache.jackrabbit.oak.blob.cloud.s3.S3DataStore; |
| import org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreBlobStore; |
| import org.apache.jackrabbit.oak.plugins.blob.datastore.OakFileDataStore; |
| import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; |
| import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder; |
| import org.apache.jackrabbit.oak.plugins.document.LeaseCheckMode; |
| import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDataSourceFactory; |
| import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection; |
| import org.apache.jackrabbit.oak.run.cli.DummyDataStore; |
| import org.apache.jackrabbit.oak.segment.SegmentNodeStoreBuilders; |
| import org.apache.jackrabbit.oak.segment.file.FileStore; |
| import org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException; |
| import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore; |
| import org.apache.jackrabbit.oak.spi.state.NodeStore; |
| import org.jetbrains.annotations.Nullable; |
| |
| import com.google.common.collect.Maps; |
| import com.google.common.io.Closer; |
| import com.google.common.io.Files; |
| import com.mongodb.MongoClientURI; |
| import com.mongodb.MongoURI; |
| |
| import joptsimple.ArgumentAcceptingOptionSpec; |
| import joptsimple.OptionParser; |
| import joptsimple.OptionSet; |
| import joptsimple.OptionSpec; |
| |
| class Utils { |
| |
| private static final long MB = 1024 * 1024; |
| |
| public static class NodeStoreOptions { |
| |
| public final OptionParser parser; |
| public final OptionSpec<String> rdbjdbcuser; |
| public final OptionSpec<String> rdbjdbcpasswd; |
| public final OptionSpec<Integer> clusterId; |
| public final OptionSpec<Void> disableBranchesSpec; |
| public final OptionSpec<Integer> cacheSizeSpec; |
| public final OptionSpec<?> help; |
| public final OptionSpec<String> nonOption; |
| |
| protected OptionSet options; |
| |
| public NodeStoreOptions(String usage) { |
| parser = new OptionParser(); |
| rdbjdbcuser = parser |
| .accepts("rdbjdbcuser", "RDB JDBC user") |
| .withOptionalArg().defaultsTo(""); |
| rdbjdbcpasswd = parser |
| .accepts("rdbjdbcpasswd", "RDB JDBC password") |
| .withOptionalArg().defaultsTo(""); |
| clusterId = parser |
| .accepts("clusterId", "MongoMK clusterId") |
| .withRequiredArg().ofType(Integer.class).defaultsTo(0); |
| disableBranchesSpec = parser. |
| accepts("disableBranches", "disable branches"); |
| cacheSizeSpec = parser. |
| accepts("cacheSize", "cache size") |
| .withRequiredArg().ofType(Integer.class).defaultsTo(0); |
| help = parser.acceptsAll(asList("h", "?", "help"),"show help").forHelp(); |
| nonOption = parser.nonOptions(usage); |
| } |
| |
| public NodeStoreOptions parse(String[] args) { |
| assert(options == null); |
| options = parser.parse(args); |
| return this; |
| } |
| |
| public void printHelpOn(OutputStream sink) throws IOException { |
| parser.printHelpOn(sink); |
| System.exit(2); |
| } |
| |
| public String getStoreArg() { |
| List<String> nonOptions = nonOption.values(options); |
| return nonOptions.size() > 0? nonOptions.get(0) : ""; |
| } |
| |
| public List<String> getOtherArgs() { |
| List<String> args = new ArrayList<String>(nonOption.values(options)); |
| if (args.size() > 0) { |
| args.remove(0); |
| } |
| return args; |
| } |
| |
| public int getClusterId() { |
| return clusterId.value(options); |
| } |
| |
| public boolean disableBranchesSpec() { |
| return options.has(disableBranchesSpec); |
| } |
| |
| public int getCacheSize() { |
| return cacheSizeSpec.value(options); |
| } |
| |
| public String getRDBJDBCUser() { |
| return rdbjdbcuser.value(options); |
| } |
| |
| public String getRDBJDBCPassword() { |
| return rdbjdbcpasswd.value(options); |
| } |
| } |
| |
| public static NodeStore bootstrapNodeStore(String[] args, Closer closer, String h) throws IOException, InvalidFileStoreVersionException { |
| return bootstrapNodeStore(new NodeStoreOptions(h).parse(args), closer); |
| } |
| |
| public static NodeStore bootstrapNodeStore(NodeStoreOptions options, Closer closer) throws IOException, InvalidFileStoreVersionException { |
| String src = options.getStoreArg(); |
| if (src == null || src.length() == 0) { |
| options.printHelpOn(System.err); |
| System.exit(1); |
| } |
| |
| if (src.startsWith(MongoURI.MONGODB_PREFIX) || src.startsWith("jdbc")) { |
| DocumentNodeStoreBuilder<?> builder = createDocumentMKBuilder(options, closer); |
| if (builder != null) { |
| DocumentNodeStore store = builder.build(); |
| closer.register(asCloseable(store)); |
| return store; |
| } |
| } |
| |
| FileStore fileStore = fileStoreBuilder(new File(src)) |
| .withStrictVersionCheck(true) |
| .build(); |
| closer.register(fileStore); |
| return SegmentNodeStoreBuilders.builder(fileStore).build(); |
| } |
| |
| @Nullable |
| static DocumentNodeStoreBuilder<?> createDocumentMKBuilder(NodeStoreOptions options, |
| Closer closer) |
| throws IOException { |
| String src = options.getStoreArg(); |
| if (src == null || src.length() == 0) { |
| options.printHelpOn(System.err); |
| System.exit(1); |
| } |
| DocumentNodeStoreBuilder<?> builder; |
| if (src.startsWith(MongoURI.MONGODB_PREFIX)) { |
| MongoClientURI uri = new MongoClientURI(src); |
| if (uri.getDatabase() == null) { |
| System.err.println("Database missing in MongoDB URI: " |
| + uri.getURI()); |
| System.exit(1); |
| } |
| MongoConnection mongo = new MongoConnection(uri.getURI()); |
| closer.register(asCloseable(mongo)); |
| builder = newMongoDocumentNodeStoreBuilder().setMongoDB( |
| mongo.getMongoClient(), mongo.getDBName()); |
| } else if (src.startsWith("jdbc")) { |
| DataSource ds = RDBDataSourceFactory.forJdbcUrl(src, |
| options.getRDBJDBCUser(), options.getRDBJDBCPassword()); |
| builder = newRDBDocumentNodeStoreBuilder().setRDBConnection(ds); |
| } else { |
| return null; |
| } |
| builder. |
| setLeaseCheckMode(LeaseCheckMode.DISABLED). |
| setClusterId(options.getClusterId()); |
| if (options.disableBranchesSpec()) { |
| builder.disableBranches(); |
| } |
| int cacheSize = options.getCacheSize(); |
| if (cacheSize != 0) { |
| builder.memoryCacheSize(cacheSize * MB); |
| } |
| return builder; |
| } |
| |
| @Nullable |
| public static GarbageCollectableBlobStore bootstrapDataStore(String[] args, Closer closer) |
| throws IOException, RepositoryException { |
| OptionParser parser = new OptionParser(); |
| parser.allowsUnrecognizedOptions(); |
| |
| ArgumentAcceptingOptionSpec<String> s3dsConfig = |
| parser.accepts("s3ds", "S3DataStore config").withRequiredArg().ofType(String.class); |
| ArgumentAcceptingOptionSpec<String> fdsConfig = |
| parser.accepts("fds", "FileDataStore config").withRequiredArg().ofType(String.class); |
| ArgumentAcceptingOptionSpec<String> azureBlobDSConfig = |
| parser.accepts("azureblobds", "AzureBlobStorageDataStore config").withRequiredArg().ofType(String.class); |
| OptionSpecBuilder nods = parser.accepts("nods", "No DataStore "); |
| |
| |
| OptionSet options = parser.parse(args); |
| |
| if (!options.has(s3dsConfig) && !options.has(fdsConfig) && !options.has(azureBlobDSConfig) && !options.has(nods)) { |
| return null; |
| } |
| |
| DataStore delegate; |
| if (options.has(s3dsConfig)) { |
| S3DataStore s3ds = new S3DataStore(); |
| String cfgPath = s3dsConfig.value(options); |
| Properties props = loadAndTransformProps(cfgPath); |
| s3ds.setProperties(props); |
| File homeDir = Files.createTempDir(); |
| closer.register(asCloseable(homeDir)); |
| s3ds.init(homeDir.getAbsolutePath()); |
| delegate = s3ds; |
| } else if (options.has(azureBlobDSConfig)) { |
| AzureDataStore azureds = new AzureDataStore(); |
| String cfgPath = azureBlobDSConfig.value(options); |
| Properties props = loadAndTransformProps(cfgPath); |
| azureds.setProperties(props); |
| File homeDir = Files.createTempDir(); |
| azureds.init(homeDir.getAbsolutePath()); |
| closer.register(asCloseable(homeDir)); |
| delegate = azureds; |
| } else if (options.has(nods)){ |
| delegate = new DummyDataStore(); |
| File homeDir = Files.createTempDir(); |
| delegate.init(homeDir.getAbsolutePath()); |
| closer.register(asCloseable(homeDir)); |
| } |
| else { |
| delegate = new OakFileDataStore(); |
| String cfgPath = fdsConfig.value(options); |
| Properties props = loadAndTransformProps(cfgPath); |
| populate(delegate, asMap(props), true); |
| delegate.init(null); |
| } |
| DataStoreBlobStore blobStore = new DataStoreBlobStore(delegate); |
| closer.register(Utils.asCloseable(blobStore)); |
| |
| return blobStore; |
| } |
| |
| static Closeable asCloseable(final DocumentNodeStore dns) { |
| return new Closeable() { |
| |
| @Override |
| public void close() throws IOException { |
| dns.dispose(); |
| } |
| }; |
| } |
| |
| private static Closeable asCloseable(final MongoConnection con) { |
| return new Closeable() { |
| |
| @Override |
| public void close() throws IOException { |
| con.close(); |
| } |
| }; |
| } |
| |
| static Closeable asCloseable(final DataStoreBlobStore blobStore) { |
| return new Closeable() { |
| |
| @Override |
| public void close() throws IOException { |
| try { |
| blobStore.close(); |
| } catch (DataStoreException e) { |
| throw new IOException(e); |
| } |
| } |
| }; |
| } |
| |
| static Closeable asCloseable(final File dir) { |
| return new Closeable() { |
| |
| @Override |
| public void close() throws IOException { |
| FileUtils.deleteDirectory(dir); |
| } |
| }; |
| } |
| |
| |
| private static Properties loadAndTransformProps(String cfgPath) throws IOException { |
| Dictionary dict = ConfigurationHandler.read(new FileInputStream(cfgPath)); |
| Properties props = new Properties(); |
| Enumeration keys = dict.keys(); |
| while (keys.hasMoreElements()) { |
| String key = (String) keys.nextElement(); |
| props.put(key, dict.get(key)); |
| } |
| return props; |
| } |
| |
| private static Map<String, ?> asMap(Properties props) { |
| Map<String, Object> map = Maps.newHashMap(); |
| for (Object key : props.keySet()) { |
| map.put((String)key, props.get(key)); |
| } |
| return map; |
| } |
| } |