blob: c6bcd3bc7cc7ba59f5510ccb71234844ba3ec138 [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.jackrabbit.oak.http;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map.Entry;
import javax.jcr.Credentials;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.SimpleCredentials;
import javax.security.auth.login.LoginException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
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.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.util.Base64;
import org.apache.tika.mime.MediaType;
public class OakServlet extends HttpServlet {
private static final MediaType JSON =
MediaType.parse("application/json");
private static final MediaType SMILE =
MediaType.parse("application/x-jackson-smile");
private static final Representation[] REPRESENTATIONS = {
new HtmlRepresentation(),
new TextRepresentation(),
new JsonRepresentation(JSON, new JsonFactory()),
new JsonRepresentation(SMILE, new SmileFactory()),
new PostRepresentation() };
private final ContentRepository repository;
public OakServlet(ContentRepository repository) {
this.repository = repository;
}
@Override
protected void service(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
Credentials credentials = null;
String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.startsWith("Basic ")) {
String[] basic =
Base64.decode(authorization.substring("Basic ".length())).split(":");
credentials = new SimpleCredentials(basic[0], basic[1].toCharArray());
} else {
throw new LoginException();
}
ContentSession session = repository.login(credentials, null);
try {
Root root = session.getLatestRoot();
request.setAttribute("root", root);
// Find the longest part of the given path that matches
// an existing node. The tail part might be used when
// creating new nodes or when exposing virtual resources.
// Note that we need to traverse the path in reverse
// direction as some parent nodes may be read-protected.
String head = request.getPathInfo();
String tail = "";
Tree tree = root.getTree(head);
while (!tree.exists()) {
if (tree.isRoot()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
tail = "/" + tree.getName() + tail;
tree = tree.getParent();
}
request.setAttribute("tree", tree);
request.setAttribute("path", tail);
super.service(request, response);
} finally {
session.close();
}
} catch (NoSuchWorkspaceException e) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
} catch (LoginException e) {
response.setHeader("WWW-Authenticate", "Basic realm=\"Oak\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
@Override
protected void doGet(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
AcceptHeader accept = new AcceptHeader(request.getHeader("Accept"));
Representation representation = accept.resolve(REPRESENTATIONS);
String path = (String) request.getAttribute("path");
if (path.isEmpty()) {
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);
}
}
@Override
protected void doPost(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
Root root = (Root) request.getAttribute("root");
Tree tree = (Tree) request.getAttribute("tree");
String path = (String) request.getAttribute("path");
for (String name : PathUtils.elements(path)) {
tree = tree.addChild(name);
}
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(request.getInputStream());
if (node.isObject()) {
post(node, tree);
root.commit();
request.setAttribute("path", "");
request.setAttribute("tree", tree);
doGet(request, response);
} else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
} catch (CommitFailedException e) {
throw new ServletException(e);
}
}
private static void post(JsonNode node, Tree tree) {
Iterator<Entry<String, JsonNode>> iterator = node.fields();
while (iterator.hasNext()) {
Entry<String, JsonNode> entry = iterator.next();
String name = entry.getKey();
JsonNode value = entry.getValue();
if (value.isObject()) {
if (tree.hasProperty(name)) {
tree.removeProperty(name);
}
Tree child = tree.getChild(name);
if (!child.exists()) {
child = tree.addChild(name);
}
post(value, child);
} else {
Tree child = tree.getChild(name);
if (child.exists()) {
child.remove();
}
if (value.isNull()) {
tree.removeProperty(name);
} else if (value.isBoolean()) {
tree.setProperty(name, value.asBoolean());
} else if (value.isLong()) {
tree.setProperty(name, value.asLong());
} else if (value.isDouble()) {
tree.setProperty(name, value.asDouble());
} else if (value.isBigDecimal()) {
tree.setProperty(name, value.decimalValue());
} else {
tree.setProperty(name, value.asText());
}
}
}
}
@Override
protected void doDelete(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
Root root = (Root) request.getAttribute("root");
Tree tree = (Tree) request.getAttribute("tree");
if (!tree.isRoot()) {
Tree parent = tree.getParent();
Tree child = parent.getChild(tree.getName());
if (child.exists()) {
child.remove();
}
root.commit();
response.sendError(HttpServletResponse.SC_OK);
} else {
// Can't remove the root node
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
} catch (CommitFailedException e) {
throw new ServletException(e);
}
}
}