blob: 614f44f380ed5374e436185731775c7ab5fa3cf2 [file] [log] [blame]
/*
* 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.sling.mvresource.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.sling.api.adapter.AdapterFactory;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.spi.resource.provider.ProviderContext;
import org.apache.sling.spi.resource.provider.ResolveContext;
import org.apache.sling.spi.resource.provider.ResourceContext;
import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;
import org.h2.mvstore.StreamStore;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(service = { ResourceProvider.class, AdapterFactory.class }, property = {
Constants.SERVICE_DESCRIPTION + "=Sling Mv Resource Provider",
Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
ResourceProvider.PROPERTY_ROOT + "=/content/apache/fake", ResourceProvider.PROPERTY_MODIFIABLE + "=true",
"adaptables=org.apache.sling.mvresource.impl.MvResource",
"adapters=org.apache.sling.api.resource.ModifiableValueMap",
ResourceProvider.PROPERTY_AUTHENTICATE + "=" + ResourceProvider.AUTHENTICATE_REQUIRED })
public class MvStoreResourceProvider extends ResourceProvider<MvSession> implements AdapterFactory {
private static final Logger LOG = LoggerFactory.getLogger(MvStoreResourceProvider.class);
private static final String CHILDREN = "_children";
@ObjectClassDefinition(name = "Apache Sling Resource Provider", description = "Configure an instance of the file system "
+ "resource provider in terms of provider root and file system location")
public @interface Config {
@AttributeDefinition(name = "DataStore File Name", description = "File system directory mapped to the virtual "
+ "resource tree. This property must not be an empty string. If the path is "
+ "relative it is resolved against sling.home or the current working directory. "
+ "The path may be a file or folder. If the path does not address an existing "
+ "file or folder, an empty folder is created.")
String provider_file();
@AttributeDefinition(name = "Provider Root", description = "Location in the virtual resource tree where the "
+ "file system resources are mapped in. This property must not be an empty string.")
String provider_root();
@AttributeDefinition(name = "Cache Size", description = "Cache size in MB")
int provider_cache_size() default 1024;
// Internal Name hint for web console.
String webconsole_configurationFactory_nameHint() default "{provider.fs.mode}: {"
+ ResourceProvider.PROPERTY_ROOT + "}";
}
MVStore store;
private StreamStore binaryStore;
@Override
public void start(ProviderContext ctx) {
LOG.info("mvprovider has started");
super.start(ctx);
store = MVStore.open("dataStore");
binaryStore = new StreamStore(store.openMap("_binaries"));
}
@Override
public void stop() {
super.stop();
store.close();
}
@Override
public Resource getResource(ResolveContext<MvSession> context, String resourcePath, ResourceContext resourceContext,
Resource parentResource) {
LOG.info("GET {} ", resourcePath);
MVMap<String, Object> properties = store.openMap(resourcePath);
if (resourcePath.equals("/content/apache/fake")) {
final ResourceProvider rp = context.getParentResourceProvider();
return rp.getResource(context.getParentResolveContext(), resourcePath, resourceContext, parentResource);
}
if (properties.isEmpty()) {
return null;
}
return new MvResource(context.getResourceResolver(), resourcePath, new MvValueMap(properties, binaryStore));
}
@Override
public Resource create(ResolveContext<MvSession> ctx, String path, Map<String, Object> properties)
throws PersistenceException {
LOG.info("CREATE {} ", path);
MVMap<String, Object> oldProps = store.openMap(path);
if (oldProps.isEmpty()) {
String parent = parentPath(path);
MVMap<String, String[]> parentResource = store.openMap(CHILDREN);
String[] children = parentResource.getOrDefault(parent, new String[] {});
String[] newChildren = Arrays.copyOf(children, children.length + 1);
newChildren[children.length] = path;
parentResource.put(parent, newChildren);
}
MvValueMap data = new MvValueMap(oldProps, binaryStore);
data.putAll(properties);
store.commit();
return new MvResource(ctx.getResourceResolver(), path, data);
}
public String currentName(String path) {
LOG.info("CURRENT NAME {} ", path);
int index = path.lastIndexOf('/');
return path.substring(index + 1, path.length());
}
private String parentPath(String path) {
int index = path.lastIndexOf('/');
return path.substring(0, index);
}
@Override
public void commit(ResolveContext<MvSession> ctx) throws PersistenceException {
LOG.info("COMMIT {} ", ctx.getProviderState());
store.commit();
}
@Override
public void delete(ResolveContext<MvSession> ctx, Resource resource) throws PersistenceException {
LOG.info("DELETE {} ", resource.getName());
if (!(resource instanceof MvResource)) {
throw new PersistenceException("can not delete resource of type" + resource.getClass());
}
MVMap<String, String[]> parentResource = store.openMap(CHILDREN);
String parentPath = parentPath(resource.getPath());
String[] childNames = parentResource.get(parentPath);
String[] newChildren = new String[childNames.length - 1];
int newIndex = 0;
for (int index = 0; index < childNames.length; ++index) {
if (!childNames[index].equals(resource.getName())) {
newChildren[newIndex++] = childNames[index];
}
}
parentResource.put(parentPath, newChildren);
Deque<String> resourceToDelete = new LinkedList<>();
resourceToDelete.add(resource.getPath());
deleteDescendents(parentResource, resourceToDelete);
}
private void deleteDescendents(MVMap<String, String[]> parentMap, Deque<String> pathsToDelete) {
if (pathsToDelete.isEmpty()) {
return;
}
String currentPath = pathsToDelete.pop();
String[] children = parentMap.getOrDefault(currentPath, new String[] {});
store.removeMap(store.openMap(currentPath));
for (int i = 0; i < children.length; ++i) {
pathsToDelete.add(children[i]);
}
deleteDescendents(parentMap, pathsToDelete);
}
@Override
public Iterator<Resource> listChildren(ResolveContext<MvSession> resolveContext, Resource resource) {
LOG.info("LIST CHILDREN");
MVMap<String, String[]> parentMap = store.openMap(CHILDREN);
List<Resource> response = new ArrayList<>();
String[] childNames = parentMap.getOrDefault(resource.getPath(), new String[] {});
for (int i = 0; i < childNames.length; ++i) {
String childPath = childNames[i];
LOG.info("child found {}", childPath);
response.add(new MvResource(resolveContext.getResourceResolver(), childPath,
new MvValueMap(store.openMap(childPath), binaryStore)));
}
return response.iterator();
}
@SuppressWarnings("unchecked")
@Override
public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) {
return (AdapterType) ((MvResource) adaptable).getValueMap();
}
}