001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.jdbc.rest;
017
018import java.io.ByteArrayInputStream;
019import javax.jcr.query.Query;
020import org.modeshape.common.util.CheckArg;
021import org.modeshape.jdbc.JdbcI18n;
022
023/**
024 * A simple java client which communicates via a {@link JSONRestClient} to an existing ModeShape REST service and unmarshals the
025 * JSON objects into a custom POJO model.
026 *
027 * @author Horia Chiorean (hchiorea@redhat.com)
028 */
029public final class ModeShapeRestClient {
030
031    private static final String NODE_TYPES_SEGMENT = "jcr:system/jcr:nodeTypes?depth=-1";
032    private static final String ITEMS_METHOD = "items";
033    private static final String QUERY_METHOD = "query";
034    private static final String QUERY_PLAN_METHOD = "queryPlan";
035
036    private final JSONRestClient jsonRestClient;
037
038    /**
039     * Creates a new REST client instance which will always use the given URL as the server connection
040     *
041     * @param repoUrl a {@code String} representing a connection to a ModeShape REST service in the format:
042     *        [protocol]://[host]:[port]/[context]/[repository]/[workspace]. May not be {@code null}
043     * @param username a {@code String} representing the name of the user to use when authenticating with the above server. May be
044     *        {@code null}, in which case no authentication will be performed.
045     * @param password a {@code String} the password of the above user, if used. May be {@code null}
046     */
047    public ModeShapeRestClient( String repoUrl,
048                                String username,
049                                String password ) {
050        CheckArg.isNotNull(repoUrl, "repoUrl");
051        this.jsonRestClient = new JSONRestClient(repoUrl, username, password);
052    }
053
054    /**
055     * Returns the URL this client uses to connect to the server.
056     * 
057     * @return a {@code String}, never {@code null}
058     */
059    public String serverUrl() {
060        return jsonRestClient.url();
061    }
062
063    /**
064     * Returns a list with all the available repositories.
065     *
066     * @return a {@link Repositories} instance, never {@code null}
067     */
068    public Repositories getRepositories() {
069        JSONRestClient.Response response = jsonRestClient.doGet();
070        if (!response.isOK()) {
071            throw new RuntimeException(JdbcI18n.invalidServerResponse.text(jsonRestClient.url(), response.asString()));
072        }
073        return new Repositories(response.json());
074    }
075
076    /**
077     * Returns a repository which has the given name or {@code null}.
078     *
079     * @param name the name of a repository; may not be null
080     * @return a {@link Repositories.Repository} instace or {@code null}
081     */
082    public Repositories.Repository getRepository( String name ) {
083        JSONRestClient.Response response = jsonRestClient.doGet();
084        if (!response.isOK()) {
085            throw new RuntimeException(JdbcI18n.invalidServerResponse.text(jsonRestClient.url(), response.asString()));
086        }
087        return new Repositories(response.json()).getRepository(name);
088    }
089
090    /**
091     * Returns all the workspaces for the named repository.
092     *
093     * @param repositoryName a {@code String} the name of a repository; may not be {@code null}
094     * @return a {@link Workspaces} instance; never {@code null}
095     */
096    public Workspaces getWorkspaces( String repositoryName ) {
097        String url = jsonRestClient.appendToBaseURL(repositoryName);
098        JSONRestClient.Response response = jsonRestClient.doGet(url);
099        if (!response.isOK()) {
100            throw new RuntimeException(JdbcI18n.invalidServerResponse.text(url, response.asString()));
101        }
102        return new Workspaces(response.json());
103    }
104
105    /**
106     * Returns all the node types that are available in the repository from {@code repoUrl}
107     *
108     * @return a {@link NodeTypes} instance; never {@code null}
109     */
110    public NodeTypes getNodeTypes() {
111        String url = jsonRestClient.appendToURL(ITEMS_METHOD, NODE_TYPES_SEGMENT);
112        JSONRestClient.Response response = jsonRestClient.doGet(url);
113        if (!response.isOK()) {
114            throw new RuntimeException(JdbcI18n.invalidServerResponse.text(url, response.asString()));
115        }
116        return new NodeTypes(response.json());
117    }
118
119    /**
120     * Runs a query in the specified language against the repository from {@code repoUrl}.
121     *
122     * @param query a {@code String}, never {@code null}
123     * @param queryLanguage the language of the query, never {@code null}
124     * @return a {@link QueryResult} instance, never {@code null}
125     * @see javax.jcr.query.Query
126     */
127    public QueryResult query( String query,
128                              String queryLanguage ) {
129        String url = jsonRestClient.appendToURL(QUERY_METHOD);
130        String contentType = contentTypeForQueryLanguage(queryLanguage);
131        JSONRestClient.Response response = jsonRestClient.postStream(new ByteArrayInputStream(query.getBytes()), url, contentType);
132        if (!response.isOK()) {
133            throw new RuntimeException(JdbcI18n.invalidServerResponse.text(url, response.asString()));
134        }
135        return new QueryResult(response.json());
136    }
137
138    /**
139     * Returns a string representation of a query plan in a given language.
140     * 
141     * @param query a {@code String}, never {@code null}
142     * @param queryLanguage the language of the query, never {@code null}
143     * @return a {@code String} description of the plan, never {@code null}
144     */
145    public String queryPlan( String query,
146                             String queryLanguage ) {
147        String url = jsonRestClient.appendToURL(QUERY_PLAN_METHOD);
148        String contentType = contentTypeForQueryLanguage(queryLanguage);
149        JSONRestClient.Response response = jsonRestClient.postStreamTextPlain(new ByteArrayInputStream(query.getBytes()), url,
150                                                                              contentType);
151        if (!response.isOK()) {
152            throw new RuntimeException(JdbcI18n.invalidServerResponse.text(url, response.asString()));
153        }
154        return response.asString();
155    }
156
157    @SuppressWarnings( "deprecation" )
158    private String contentTypeForQueryLanguage( String queryLanguage ) {
159        switch (queryLanguage) {
160            case Query.XPATH: {
161                return "application/jcr+xpath";
162            }
163            case Query.SQL: {
164                return "application/jcr+sql";
165            }
166            case Query.JCR_SQL2: {
167                return "application/jcr+sql2";
168            }
169            case Query.JCR_JQOM: {
170                return "application/jcr+search";
171            }
172            default: {
173                throw new IllegalArgumentException("Invalid query language: " + queryLanguage);
174            }
175        }
176    }
177}