blob: aa192fc1a65b3d26179abd1258d71e11a767fca7 [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.hugegraph.api.schema;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.api.API;
import org.apache.hugegraph.api.filter.RedirectFilter;
import org.apache.hugegraph.api.filter.StatusFilter.Status;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.core.GraphManager;
import org.apache.hugegraph.define.Checkable;
import org.apache.hugegraph.schema.IndexLabel;
import org.apache.hugegraph.schema.SchemaElement;
import org.apache.hugegraph.schema.Userdata;
import org.apache.hugegraph.type.HugeType;
import org.apache.hugegraph.type.define.GraphMode;
import org.apache.hugegraph.type.define.IndexType;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;
import com.codahale.metrics.annotation.Timed;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableMap;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Singleton;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
@Path("graphs/{graph}/schema/indexlabels")
@Singleton
@Tag(name = "IndexLabelAPI")
public class IndexLabelAPI extends API {
private static final Logger LOG = Log.logger(IndexLabelAPI.class);
@POST
@Timed
@Status(Status.ACCEPTED)
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RolesAllowed({"admin", "$owner=$graph $action=index_label_write"})
@RedirectFilter.RedirectMasterRole
public String create(@Context GraphManager manager,
@PathParam("graph") String graph,
JsonIndexLabel jsonIndexLabel) {
LOG.debug("Graph [{}] create index label: {}", graph, jsonIndexLabel);
checkCreatingBody(jsonIndexLabel);
HugeGraph g = graph(manager, graph);
IndexLabel.Builder builder = jsonIndexLabel.convert2Builder(g);
SchemaElement.TaskWithSchema il = builder.createWithTask();
il.indexLabel(mapIndexLabel(il.indexLabel()));
return manager.serializer(g).writeTaskWithSchema(il);
}
@PUT
@Timed
@Path("{name}")
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RedirectFilter.RedirectMasterRole
public String update(@Context GraphManager manager,
@PathParam("graph") String graph,
@PathParam("name") String name,
@QueryParam("action") String action,
IndexLabelAPI.JsonIndexLabel jsonIndexLabel) {
LOG.debug("Graph [{}] {} index label: {}",
graph, action, jsonIndexLabel);
checkUpdatingBody(jsonIndexLabel);
E.checkArgument(name.equals(jsonIndexLabel.name),
"The name in url(%s) and body(%s) are different",
name, jsonIndexLabel.name);
// Parse action parameter
boolean append = checkAndParseAction(action);
HugeGraph g = graph(manager, graph);
IndexLabel.Builder builder = jsonIndexLabel.convert2Builder(g);
IndexLabel indexLabel = append ? builder.append() : builder.eliminate();
return manager.serializer(g).writeIndexlabel(mapIndexLabel(indexLabel));
}
@GET
@Timed
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RolesAllowed({"admin", "$owner=$graph $action=index_label_read"})
public String list(@Context GraphManager manager,
@PathParam("graph") String graph,
@QueryParam("names") List<String> names) {
boolean listAll = CollectionUtils.isEmpty(names);
if (listAll) {
LOG.debug("Graph [{}] list index labels", graph);
} else {
LOG.debug("Graph [{}] get index labels by names {}", graph, names);
}
HugeGraph g = graph(manager, graph);
List<IndexLabel> labels;
if (listAll) {
labels = g.schema().getIndexLabels();
} else {
labels = new ArrayList<>(names.size());
for (String name : names) {
labels.add(g.schema().getIndexLabel(name));
}
}
return manager.serializer(g).writeIndexlabels(mapIndexLabels(labels));
}
@GET
@Timed
@Path("{name}")
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RolesAllowed({"admin", "$owner=$graph $action=index_label_read"})
public String get(@Context GraphManager manager,
@PathParam("graph") String graph,
@PathParam("name") String name) {
LOG.debug("Graph [{}] get index label by name '{}'", graph, name);
HugeGraph g = graph(manager, graph);
IndexLabel indexLabel = g.schema().getIndexLabel(name);
return manager.serializer(g).writeIndexlabel(mapIndexLabel(indexLabel));
}
@DELETE
@Timed
@Path("{name}")
@Status(Status.ACCEPTED)
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RolesAllowed({"admin", "$owner=$graph $action=index_label_delete"})
@RedirectFilter.RedirectMasterRole
public Map<String, Id> delete(@Context GraphManager manager,
@PathParam("graph") String graph,
@PathParam("name") String name) {
LOG.debug("Graph [{}] remove index label by name '{}'", graph, name);
HugeGraph g = graph(manager, graph);
// Throw 404 if not exists
g.schema().getIndexLabel(name);
return ImmutableMap.of("task_id",
g.schema().indexLabel(name).remove());
}
private static List<IndexLabel> mapIndexLabels(List<IndexLabel> labels) {
List<IndexLabel> results = new ArrayList<>(labels.size());
for (IndexLabel il : labels) {
results.add(mapIndexLabel(il));
}
return results;
}
/**
* Map RANGE_INT/RANGE_FLOAT/RANGE_LONG/RANGE_DOUBLE to RANGE
*/
private static IndexLabel mapIndexLabel(IndexLabel label) {
if (label.indexType().isRange()) {
label = (IndexLabel) label.copy();
label.indexType(IndexType.RANGE);
}
return label;
}
/**
* JsonIndexLabel is only used to receive create and append requests
*/
@JsonIgnoreProperties(value = {"status"})
private static class JsonIndexLabel implements Checkable {
@JsonProperty("id")
public long id;
@JsonProperty("name")
public String name;
@JsonProperty("base_type")
public HugeType baseType;
@JsonProperty("base_value")
public String baseValue;
@JsonProperty("index_type")
public IndexType indexType;
@JsonProperty("fields")
public String[] fields;
@JsonProperty("user_data")
public Userdata userdata;
@JsonProperty("check_exist")
public Boolean checkExist;
@JsonProperty("rebuild")
public Boolean rebuild;
@Override
public void checkCreate(boolean isBatch) {
E.checkArgumentNotNull(this.name,
"The name of index label can't be null");
E.checkArgumentNotNull(this.baseType,
"The base type of index label '%s' " +
"can't be null", this.name);
E.checkArgument(this.baseType == HugeType.VERTEX_LABEL ||
this.baseType == HugeType.EDGE_LABEL,
"The base type of index label '%s' can only be " +
"either VERTEX_LABEL or EDGE_LABEL", this.name);
E.checkArgumentNotNull(this.baseValue,
"The base value of index label '%s' " +
"can't be null", this.name);
E.checkArgumentNotNull(this.indexType,
"The index type of index label '%s' " +
"can't be null", this.name);
}
@Override
public void checkUpdate() {
E.checkArgumentNotNull(this.name,
"The name of index label can't be null");
E.checkArgument(this.baseType == null,
"The base type of index label '%s' must be null",
this.name);
E.checkArgument(this.baseValue == null,
"The base value of index label '%s' must be null",
this.name);
E.checkArgument(this.indexType == null,
"The index type of index label '%s' must be null",
this.name);
}
private IndexLabel.Builder convert2Builder(HugeGraph g) {
IndexLabel.Builder builder = g.schema().indexLabel(this.name);
if (this.id != 0) {
E.checkArgument(this.id > 0,
"Only positive number can be assign as " +
"index label id");
E.checkArgument(g.mode() == GraphMode.RESTORING,
"Only accept index label id when graph in " +
"RESTORING mode, but '%s' is in mode '%s'",
g, g.mode());
builder.id(this.id);
}
if (this.baseType != null) {
assert this.baseValue != null;
builder.on(this.baseType, this.baseValue);
}
if (this.indexType != null) {
builder.indexType(this.indexType);
}
if (this.fields != null && this.fields.length > 0) {
builder.by(this.fields);
}
if (this.userdata != null) {
builder.userdata(this.userdata);
}
if (this.checkExist != null) {
builder.checkExist(this.checkExist);
}
if (this.rebuild != null) {
builder.rebuild(this.rebuild);
}
return builder;
}
@Override
public String toString() {
return String.format("JsonIndexLabel{name=%s, baseType=%s," +
"baseValue=%s, indexType=%s, fields=%s}",
this.name, this.baseType, this.baseValue,
this.indexType, Arrays.toString(this.fields));
}
}
}