blob: a75e72c7d7bdde81df5a062a4d7f05cc716b335e [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.feature.cpconverter.index;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import javax.jcr.PropertyType;
import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonGenerator;
import org.apache.jackrabbit.util.Base64;
import org.apache.jackrabbit.vault.util.DocViewNode2;
import org.apache.jackrabbit.vault.util.DocViewProperty2;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Writes index definitions in a JSON format that can be consumed by the {@code oak-run} tool.
*
* @see <a href=
* "https://jackrabbit.apache.org/oak/docs/query/oak-run-indexing.html">Oak-Run
* indexing</a>
*/
public class IndexDefinitionsJsonWriter {
private static final Function<String, JsonValue> BLOB_MAPPER = s -> Json.createValue(":blobid:" + Base64.encode(s));
private static final Function<String, JsonValue> SAFE_LONG_MAPPER = new Function<String, JsonValue>() {
@Override
public JsonValue apply(String t) {
if ( t.endsWith(".0") )
t = t.replace(".0", "");
return Json.createValue(Long.parseLong(t));
}
};
private final Logger logger = LoggerFactory.getLogger(getClass());
private final IndexDefinitions indexDefinitions;
public IndexDefinitionsJsonWriter(@NotNull IndexDefinitions indexDefinitions) {
this.indexDefinitions = indexDefinitions;
}
/**
* Writes the index definitions to the specified {@code out}
*
* @param out the output stream to write to
*/
public void writeAsJson(@NotNull OutputStream out) {
try ( JsonGenerator root = Json.createGenerator(out) ) {
root.writeStartObject();
for ( Map.Entry<String, List<DocViewNode2>> indexEntry : indexDefinitions.getIndexes().entrySet() )
for ( DocViewNode2 index : indexEntry.getValue() )
write(root, index, indexEntry.getKey());
root.writeEnd(); // end object declaration
}
}
private void write(JsonGenerator json, DocViewNode2 index, String parentPath) {
String nodeName = indexDefinitions.toShortName(index.getName());
String objectKey = parentPath.equals(IndexDefinitions.OAK_INDEX_PATH) ?
IndexDefinitions.OAK_INDEX_PATH + "/" + nodeName : nodeName;
// 1. start object
json.writeStartObject(objectKey);
// 2. write properties
for ( DocViewProperty2 property : index.getProperties() ) {
String propertyName = indexDefinitions.toShortName(property.getName());
switch ( property.getType() ) {
case PropertyType.STRING:
case PropertyType.UNDEFINED:
write(json, propertyName, property.getStringValues(), s -> Json.createValue("str:" + s ));
break;
case PropertyType.LONG:
write(json, propertyName, property.getStringValues(), SAFE_LONG_MAPPER );
break;
case PropertyType.BOOLEAN:
write(json, propertyName, property.getStringValues(), s -> ( Boolean.parseBoolean(s) ? JsonValue.TRUE : JsonValue.FALSE) );
break;
case PropertyType.NAME:
write(json, propertyName, property.getStringValues(), s -> Json.createValue("nam:" + s ));
break;
case PropertyType.DOUBLE:
write(json, propertyName, property.getStringValues(), s -> Json.createValue(Double.parseDouble(s) ));
break;
case PropertyType.DATE:
write(json, propertyName, property.getStringValues(), s -> Json.createValue("dat:" + s) );
break;
case PropertyType.PATH:
write(json, propertyName, property.getStringValues(), s -> Json.createValue("pat:" + s) );
break;
case PropertyType.URI:
write(json, propertyName, property.getStringValues(), s -> Json.createValue("uri:" + s) );
break;
case PropertyType.BINARY:
write(json, propertyName, property.getStringValues(), BLOB_MAPPER );
break;
default:
logger.warn("Skipping property {}, don't know how to handle type {}; values: {}", property.getName(), property.getType(), property.getStringValues());
}
}
String nodePath = parentPath + "/" + nodeName; // NOSONAR - java:S1075 does not apply as this is not a filesystem path
// 3. write nt:data entries for nt:resource children of nt:files
// in this case, this is the nt:resource node
Optional<byte[]> binary = indexDefinitions.getBinary(nodePath);
if ( binary.isPresent() ) {
String blobAsString = new String(binary.get(), StandardCharsets.UTF_8);
write(json, "jcr:data", Collections.singletonList(blobAsString), BLOB_MAPPER);
};
// 4. write children
for ( DocViewNode2 child : indexDefinitions.getChildren(nodePath)) {
write(json, child, nodePath);
}
// 5. end object
json.writeEnd();
}
private void write(JsonGenerator json, String propertyName, List<String> propertyValues, Function<String, JsonValue> mapper) {
if ( propertyValues.size() == 1 ) {
json.write(propertyName, mapper.apply(propertyValues.get(0)));
return;
}
JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
propertyValues.stream()
.map( mapper )
.forEach( arrayBuilder::add );
json.write(propertyName, arrayBuilder.build());
}
}