org.wrml.runtime.format.text.html.WrmldocFormatter.java Source code

Java tutorial

Introduction

Here is the source code for org.wrml.runtime.format.text.html.WrmldocFormatter.java

Source

/**
 * WRML - Web Resource Modeling Language
 *  __     __   ______   __    __   __
 * /\ \  _ \ \ /\  == \ /\ "-./  \ /\ \
 * \ \ \/ ".\ \\ \  __< \ \ \-./\ \\ \ \____
 *  \ \__/".~\_\\ \_\ \_\\ \_\ \ \_\\ \_____\
 *   \/_/   \/_/ \/_/ /_/ \/_/  \/_/ \/_____/
 *
 * http://www.wrml.org
 *
 * Copyright (C) 2011 - 2013 Mark Masse <mark@wrml.org> (OSS project WRML.org)
 *
 * Licensed 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.wrml.runtime.format.text.html;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.wrml.model.Model;
import org.wrml.model.rest.*;
import org.wrml.model.rest.status.ApiNotFoundErrorReport;
import org.wrml.model.rest.status.DocumentNotFoundErrorReport;
import org.wrml.model.rest.status.ErrorReport;
import org.wrml.model.rest.status.ResourceNotFoundErrorReport;
import org.wrml.model.schema.Schema;
import org.wrml.model.schema.ValueType;
import org.wrml.runtime.Context;
import org.wrml.runtime.Dimensions;
import org.wrml.runtime.Keys;
import org.wrml.runtime.format.*;
import org.wrml.runtime.format.application.vnd.wrml.design.schema.SchemaDesignFormatter;
import org.wrml.runtime.rest.*;
import org.wrml.runtime.schema.*;
import org.wrml.runtime.syntax.SyntaxLoader;
import org.wrml.util.UniqueName;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.net.URI;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * The wrmldoc formatter is spiritually akin to the <code>javadoc</code> tool in that it generates HTML from models and metadata.
 *
 * @see SystemFormat#html
 * @see <a href="http://www.wrml.org/wrmldoc/archive/common">First Introduced</a>
 * @see <a href="http://www.wrml.org/java/api/wrml-core-1.0">Javadoc Doclet Prototype</a>
 */
public class WrmldocFormatter extends AbstractFormatter {

    public static final String DOCROOT_SETTING_NAME = "docroot";

    public static final String MINIFY_SETTING_NAME = "minify";

    //public static final String DEFAULT_DOCROOT = "http://www.wrml.org/wrmldoc/";

    public static final String DEFAULT_DOCROOT = "/_wrml/wrmldoc/";

    public static final String SHELL_PAGE_TEMPLATE_RESOURCE = "index.html";

    public static final String EMPTY_OBJECT = "{}";

    private Map<String, MessageFormat> _Templates;

    private String _Docroot;

    private boolean _IsSourceCodeMinified;

    public WrmldocFormatter() {
        // Toggle this to minify (or not) the JS and CSS used in the web app.
        _IsSourceCodeMinified = false;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <M extends Model> M readModel(final InputStream in, final Keys rootModelKeys,
            final Dimensions rootModelDimensions) throws ModelReadingException, UnsupportedOperationException {

        throw new UnsupportedOperationException(
                "The \"readModel\" operation is not supported by the \"" + getFormatUri() + "\" format.");
    }

    @Override
    public void writeModel(final OutputStream out, final Model model, final ModelWriteOptions writeOptions)
            throws ModelWritingException, UnsupportedOperationException {

        final Context context = getContext();
        final SchemaLoader schemaLoader = context.getSchemaLoader();
        final URI schemaUri = model.getSchemaUri();

        final String dotMin = (_IsSourceCodeMinified) ? ".min" : "";
        final String titleSlotName = SchemaDesignFormatter.getTitleSlotName(schemaUri, schemaLoader);
        String documentTitle = "Untitled";
        String documentIcon = _Docroot + "img/model.png";

        if (StringUtils.isNotBlank(titleSlotName)) {
            documentTitle = String.valueOf(model.getSlotValue(titleSlotName));
        }

        // TODO: Replace this ObjectMapper-based implementation with the construction of a WRML model that represents
        // the wrmldoc "shell" data structure

        final ObjectMapper objectMapper = new ObjectMapper();
        try {

            final String modelValue;
            final String schemaValue;

            String linkRelationValue = EMPTY_OBJECT;

            if (model instanceof Schema) {
                modelValue = EMPTY_OBJECT;

                final Schema schema = (Schema) model;
                final ObjectNode schemaNode = SchemaDesignFormatter.createSchemaDesignObjectNode(objectMapper,
                        schema);
                schemaValue = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(schemaNode);

                documentIcon = _Docroot + "img/schema.png";
            } else if (model instanceof Api) {
                modelValue = EMPTY_OBJECT;
                schemaValue = EMPTY_OBJECT;

                documentIcon = _Docroot + "img/api.png";
            } else if (model instanceof LinkRelation) {
                modelValue = model.toString();

                final ObjectNode linkRelationNode = buildLinkRelationNode(objectMapper, (LinkRelation) model);
                linkRelationValue = objectMapper.writerWithDefaultPrettyPrinter()
                        .writeValueAsString(linkRelationNode);

                schemaValue = EMPTY_OBJECT;
                documentIcon = _Docroot + "img/linkRelation.png";
            } else if (model instanceof ErrorReport) {
                modelValue = model.toString();
                schemaValue = EMPTY_OBJECT;
                if (model instanceof ApiNotFoundErrorReport) {
                    documentIcon = _Docroot + "img/apiNotFound.png";
                } else if (model instanceof ResourceNotFoundErrorReport) {
                    documentIcon = _Docroot + "img/resourceNotFound.png";
                } else if (model instanceof DocumentNotFoundErrorReport) {
                    documentIcon = _Docroot + "img/documentNotFound.png";
                }
            } else {
                modelValue = model.toString();

                final Keys schemaKeys = context.getApiLoader().buildDocumentKeys(schemaUri,
                        schemaLoader.getSchemaSchemaUri());
                final Schema schema = context.getModel(schemaKeys, schemaLoader.getSchemaDimensions());
                final ObjectNode schemaNode = SchemaDesignFormatter.createSchemaDesignObjectNode(objectMapper,
                        schema);
                schemaValue = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(schemaNode);

            }

            final ObjectNode apiNode = buildApiNode(objectMapper, model);
            final String apiValue = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(apiNode);

            final MessageFormat pageTemplate = getTemplate(SHELL_PAGE_TEMPLATE_RESOURCE);
            final String renderedPage = renderPage(pageTemplate, _Docroot, dotMin, documentTitle, documentIcon,
                    schemaUri.toString(), modelValue, schemaValue, apiValue, linkRelationValue);

            IOUtils.write(renderedPage, out);

        } catch (IOException e) {
            throw new ModelWritingException(e.getMessage(), e, this);
        }

    }

    public MessageFormat getTemplate(String templateName) throws IOException {

        if (!_Templates.containsKey(templateName)) {
            final InputStream templateStream = getClass().getResourceAsStream(templateName);
            final String templateSource = IOUtils.toString(templateStream);

            _Templates.put(templateName, new MessageFormat(templateSource));
        }

        return _Templates.get(templateName);

    }

    protected String renderPage(final MessageFormat template, Object... params) throws IOException {

        final String renderedPage = template.format(params);
        return renderedPage;
    }

    protected ObjectNode buildApiNode(final ObjectMapper objectMapper, final Model model) {

        final ObjectNode apiNode = objectMapper.createObjectNode();
        if (!(model instanceof Document)) {
            return apiNode;
        }

        final Context context = getContext();

        final Document document = (Document) model;
        final URI uri = document.getUri();
        final ApiLoader apiLoader = context.getApiLoader();
        final ApiNavigator apiNavigator = apiLoader.getParentApiNavigator(uri);
        final Api api = apiNavigator.getApi();
        final Resource endpointResource = apiNavigator.getResource(uri);

        final URI apiUri = api.getUri();

        apiNode.put(PropertyName.uri.name(), apiUri.toString());
        apiNode.put(PropertyName.title.name(), api.getTitle());
        apiNode.put(PropertyName.description.name(), api.getDescription());
        apiNode.put(PropertyName.version.name(), api.getVersion());

        final Map<URI, ObjectNode> schemaNodes = new HashMap<>();
        final Map<URI, LinkRelation> linkRelationCache = new HashMap<>();

        if (document instanceof Api) {

            final Map<UUID, Resource> allResources = apiNavigator.getAllResources();
            final SortedMap<String, Resource> orderedResources = new TreeMap<>();

            for (final Resource resource : allResources.values()) {
                orderedResources.put(resource.getPathText(), resource);
            }

            final ArrayNode allResourcesNode = objectMapper.createArrayNode();
            for (final Resource resource : orderedResources.values()) {
                final ObjectNode resourceNode = buildResourceNode(objectMapper, schemaNodes, linkRelationCache,
                        apiNavigator, resource);
                allResourcesNode.add(resourceNode);
            }

            apiNode.put(PropertyName.allResources.name(), allResourcesNode);
        } else {
            final ObjectNode endpointResourceNode = buildResourceNode(objectMapper, schemaNodes, linkRelationCache,
                    apiNavigator, endpointResource);
            apiNode.put(PropertyName.resource.name(), endpointResourceNode);
        }

        return apiNode;
    }

    protected ObjectNode buildResourceNode(final ObjectMapper objectMapper, final Map<URI, ObjectNode> schemaNodes,
            final Map<URI, LinkRelation> linkRelationCache, final ApiNavigator apiNavigator,
            final Resource resource) {

        final Context context = getContext();
        final SchemaLoader schemaLoader = context.getSchemaLoader();
        final SyntaxLoader syntaxLoader = context.getSyntaxLoader();

        final ObjectNode resourceNode = objectMapper.createObjectNode();

        resourceNode.put(PropertyName.id.name(), syntaxLoader.formatSyntaxValue(resource.getResourceTemplateId()));
        resourceNode.put(PropertyName.pathSegment.name(), resource.getPathSegment());
        resourceNode.put(PropertyName.fullPath.name(), resource.getPathText());

        final String parentPathText = resource.getParentPathText();
        if (parentPathText != null) {
            resourceNode.put(PropertyName.parentPath.name(), parentPathText);
        }

        resourceNode.put(PropertyName.uriTemplate.name(), resource.getUriTemplate().getUriTemplateString());

        final URI defaultSchemaUri = resource.getDefaultSchemaUri();
        Prototype defaultPrototype = null;
        if (defaultSchemaUri != null) {
            final ObjectNode defaultSchemaNode = getSchemaNode(objectMapper, schemaNodes, defaultSchemaUri,
                    schemaLoader);
            resourceNode.put(PropertyName.defaultSchema.name(), defaultSchemaNode);
            defaultPrototype = schemaLoader.getPrototype(defaultSchemaUri);
        }

        final ArrayNode referencesNode = buildReferencesArrayNode(objectMapper, schemaNodes, linkRelationCache,
                resource, defaultPrototype);
        if (referencesNode.size() > 0) {
            resourceNode.put(PropertyName.references.name(), referencesNode);
        }

        final ObjectNode linksNode = buildLinksNode(objectMapper, schemaNodes, linkRelationCache, apiNavigator,
                resource, defaultPrototype);
        if (linksNode.size() > 0) {
            resourceNode.put(PropertyName.links.name(), linksNode);
        }

        return resourceNode;
    }

    protected ArrayNode buildReferencesArrayNode(final ObjectMapper objectMapper,
            final Map<URI, ObjectNode> schemaNodes, final Map<URI, LinkRelation> linkRelationCache,
            final Resource resource, final Prototype defaultPrototype) {

        final Context context = getContext();
        final SchemaLoader schemaLoader = context.getSchemaLoader();
        final SyntaxLoader syntaxLoader = context.getSyntaxLoader();

        final URI defaultSchemaUri = (defaultPrototype != null) ? defaultPrototype.getSchemaUri() : null;
        final String defaultSchemaName = (defaultPrototype != null)
                ? defaultPrototype.getUniqueName().getLocalName()
                : null;

        final ArrayNode referencesNode = objectMapper.createArrayNode();

        final ConcurrentHashMap<URI, LinkTemplate> referenceTemplates = resource.getReferenceTemplates();
        final Set<URI> referenceRelationUris = referenceTemplates.keySet();

        if (referenceTemplates != null && !referenceTemplates.isEmpty()) {

            String selfResponseSchemaName = null;

            List<String> resourceParameterList = null;
            final UriTemplate uriTemplate = resource.getUriTemplate();
            final String[] parameterNames = uriTemplate.getParameterNames();
            if (parameterNames != null && parameterNames.length > 0) {

                resourceParameterList = new ArrayList<>();

                for (int i = 0; i < parameterNames.length; i++) {
                    final String parameterName = parameterNames[i];

                    URI keyedSchemaUri = null;

                    if (defaultPrototype != null) {
                        final Set<String> allKeySlotNames = defaultPrototype.getAllKeySlotNames();
                        if (allKeySlotNames != null && allKeySlotNames.contains(parameterName)) {
                            keyedSchemaUri = defaultSchemaUri;
                        }
                    }

                    if (keyedSchemaUri == null) {

                        final Set<URI> referenceLinkRelationUris = resource
                                .getReferenceLinkRelationUris(Method.Get);
                        if (referenceLinkRelationUris != null && !referenceLinkRelationUris.isEmpty()) {
                            for (URI linkRelationUri : referenceLinkRelationUris) {
                                final LinkTemplate referenceTemplate = referenceTemplates.get(linkRelationUri);
                                final URI responseSchemaUri = referenceTemplate.getResponseSchemaUri();
                                final Prototype responseSchemaPrototype = schemaLoader
                                        .getPrototype(responseSchemaUri);
                                if (responseSchemaPrototype != null) {
                                    final Set<String> allKeySlotNames = responseSchemaPrototype
                                            .getAllKeySlotNames();
                                    if (allKeySlotNames != null && allKeySlotNames.contains(parameterName)) {
                                        keyedSchemaUri = responseSchemaUri;
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    String parameterTypeString = "?";

                    if (keyedSchemaUri != null) {

                        final Prototype keyedPrototype = schemaLoader.getPrototype(keyedSchemaUri);
                        final ProtoSlot keyProtoSlot = keyedPrototype.getProtoSlot(parameterName);
                        if (keyProtoSlot instanceof PropertyProtoSlot) {
                            final PropertyProtoSlot keyPropertyProtoSlot = (PropertyProtoSlot) keyProtoSlot;
                            final ValueType parameterValueType = keyPropertyProtoSlot.getValueType();
                            final Type parameterHeapType = keyPropertyProtoSlot.getHeapValueType();
                            switch (parameterValueType) {
                            case Text: {
                                if (!String.class.equals(parameterHeapType)) {
                                    final Class<?> syntaxClass = (Class<?>) parameterHeapType;
                                    parameterTypeString = syntaxClass.getSimpleName();
                                } else {
                                    parameterTypeString = parameterValueType.name();
                                }

                                break;
                            }
                            case SingleSelect: {
                                final Class<?> choicesEnumClass = (Class<?>) parameterHeapType;

                                if (choicesEnumClass.isEnum()) {
                                    parameterTypeString = choicesEnumClass.getSimpleName();
                                } else {
                                    // ?
                                    parameterTypeString = parameterValueType.name();
                                }

                                break;
                            }
                            default: {
                                parameterTypeString = parameterValueType.name();
                                break;
                            }
                            }
                        }

                    }

                    resourceParameterList.add(parameterTypeString + " " + parameterName);
                }
            }

            for (final Method method : Method.values()) {
                for (final URI linkRelationUri : referenceRelationUris) {

                    final LinkTemplate referenceTemplate = referenceTemplates.get(linkRelationUri);
                    final LinkRelation linkRelation = getLinkRelation(linkRelationCache, linkRelationUri);

                    if (method != linkRelation.getMethod()) {
                        continue;
                    }

                    final ObjectNode referenceNode = objectMapper.createObjectNode();
                    referencesNode.add(referenceNode);

                    referenceNode.put(PropertyName.method.name(), method.getProtocolGivenName());
                    referenceNode.put(PropertyName.rel.name(), syntaxLoader.formatSyntaxValue(linkRelationUri));

                    final String relationTitle = linkRelation.getTitle();
                    referenceNode.put(PropertyName.relationTitle.name(), relationTitle);

                    final URI responseSchemaUri = referenceTemplate.getResponseSchemaUri();
                    String responseSchemaName = null;
                    if (responseSchemaUri != null) {
                        final ObjectNode responseSchemaNode = getSchemaNode(objectMapper, schemaNodes,
                                responseSchemaUri, schemaLoader);
                        referenceNode.put(PropertyName.responseSchema.name(), responseSchemaNode);

                        responseSchemaName = responseSchemaNode
                                .get(SchemaDesignFormatter.PropertyName.localName.name()).asText();
                    }

                    final URI requestSchemaUri = referenceTemplate.getRequestSchemaUri();

                    String requestSchemaName = null;
                    if (requestSchemaUri != null) {
                        final ObjectNode requestSchemaNode = getSchemaNode(objectMapper, schemaNodes,
                                requestSchemaUri, schemaLoader);
                        referenceNode.put(PropertyName.requestSchema.name(), requestSchemaNode);

                        requestSchemaName = requestSchemaNode
                                .get(SchemaDesignFormatter.PropertyName.localName.name()).asText();
                    }

                    final StringBuilder signatureBuilder = new StringBuilder();

                    if (responseSchemaName != null) {
                        signatureBuilder.append(responseSchemaName);
                    } else {
                        signatureBuilder.append("void");
                    }

                    signatureBuilder.append(" ");

                    String functionName = relationTitle;

                    if (SystemLinkRelation.self.getUri().equals(linkRelationUri)) {
                        functionName = "get" + responseSchemaName;
                        selfResponseSchemaName = responseSchemaName;
                    } else if (SystemLinkRelation.save.getUri().equals(linkRelationUri)) {
                        functionName = "save" + responseSchemaName;
                    } else if (SystemLinkRelation.delete.getUri().equals(linkRelationUri)) {
                        functionName = "delete";
                        if (defaultSchemaName != null) {
                            functionName += defaultSchemaName;
                        } else if (selfResponseSchemaName != null) {
                            functionName += selfResponseSchemaName;
                        }
                    }

                    signatureBuilder.append(functionName).append(" ( ");

                    String parameterString = null;
                    if (resourceParameterList != null) {
                        final StringBuilder parameterStringBuilder = new StringBuilder();
                        final int parameterCount = resourceParameterList.size();
                        for (int i = 0; i < parameterCount; i++) {
                            final String parameter = resourceParameterList.get(i);
                            parameterStringBuilder.append(parameter);
                            if (i < parameterCount - 1) {
                                parameterStringBuilder.append(" , ");
                            }
                        }

                        parameterString = parameterStringBuilder.toString();
                        signatureBuilder.append(parameterString);
                    }

                    if (requestSchemaName != null) {
                        if (StringUtils.isNotBlank(parameterString)) {
                            signatureBuilder.append(" , ");
                        }

                        signatureBuilder.append(requestSchemaName);

                        signatureBuilder.append(" ");

                        final String parameterName = Character.toLowerCase(requestSchemaName.charAt(0))
                                + requestSchemaName.substring(1);
                        signatureBuilder.append(parameterName);
                    }

                    signatureBuilder.append(" ) ");

                    final String signature = signatureBuilder.toString();
                    referenceNode.put(PropertyName.signature.name(), signature);
                }

            }

        }

        return referencesNode;
    }

    protected ObjectNode buildLinksNode(final ObjectMapper objectMapper, final Map<URI, ObjectNode> schemaNodes,
            final Map<URI, LinkRelation> linkRelationCache, final ApiNavigator apiNavigator,
            final Resource resource, final Prototype defaultPrototype) {

        final Context context = getContext();
        final SchemaLoader schemaLoader = context.getSchemaLoader();
        final SyntaxLoader syntaxLoader = context.getSyntaxLoader();

        final URI defaultSchemaUri = (defaultPrototype != null) ? defaultPrototype.getSchemaUri() : null;

        final ObjectNode linksNode = objectMapper.createObjectNode();

        final Set<URI> responseSchemaUris = new HashSet<>();
        if (defaultSchemaUri != null) {
            responseSchemaUris.add(defaultSchemaUri);
        }

        for (final Method method : Method.values()) {
            final Set<URI> methodResponseSchemaUris = resource.getResponseSchemaUris(method);
            if (methodResponseSchemaUris != null && !methodResponseSchemaUris.isEmpty()) {
                responseSchemaUris.addAll(methodResponseSchemaUris);
            }
        }

        final ConcurrentHashMap<URI, LinkTemplate> linkTemplates = resource.getLinkTemplates();

        for (final URI schemaUri : responseSchemaUris) {
            final Prototype prototype = schemaLoader.getPrototype(schemaUri);
            final SortedMap<URI, LinkProtoSlot> linkProtoSlots = prototype.getLinkProtoSlots();
            if (linkProtoSlots == null || linkProtoSlots.isEmpty()) {
                continue;
            }

            final ObjectNode linkTemplatesNode = objectMapper.createObjectNode();

            final Set<URI> linkRelationUris = linkProtoSlots.keySet();
            for (final URI linkRelationUri : linkRelationUris) {
                final LinkProtoSlot linkProtoSlot = linkProtoSlots.get(linkRelationUri);

                if (schemaLoader.getDocumentSchemaUri().equals(linkProtoSlot.getDeclaringSchemaUri())) {
                    // Skip over the built-in system-level link relations (which are all self-referential).
                    continue;
                }

                final ObjectNode linkTemplateNode = objectMapper.createObjectNode();
                final String linkSlotName = linkProtoSlot.getName();
                linkTemplatesNode.put(linkSlotName, linkTemplateNode);

                final LinkRelation linkRelation = getLinkRelation(linkRelationCache, linkRelationUri);
                final Method method = linkRelation.getMethod();

                linkTemplateNode.put(PropertyName.method.name(), method.getProtocolGivenName());
                linkTemplateNode.put(PropertyName.rel.name(), syntaxLoader.formatSyntaxValue(linkRelationUri));
                linkTemplateNode.put(PropertyName.relationTitle.name(), linkRelation.getTitle());

                final URI responseSchemaUri;
                final URI requestSchemaUri;

                if (linkTemplates.containsKey(linkRelationUri)) {
                    final LinkTemplate linkTemplate = linkTemplates.get(linkRelationUri);
                    responseSchemaUri = linkTemplate.getResponseSchemaUri();
                    requestSchemaUri = linkTemplate.getRequestSchemaUri();

                    final UUID endPointId = linkTemplate.getEndPointId();
                    if (endPointId != null) {
                        final Resource endPointResource = apiNavigator.getResource(endPointId);

                        final ObjectNode endPointNode = objectMapper.createObjectNode();

                        endPointNode.put(PropertyName.id.name(), syntaxLoader.formatSyntaxValue(endPointId));
                        endPointNode.put(PropertyName.pathSegment.name(), endPointResource.getPathSegment());
                        endPointNode.put(PropertyName.fullPath.name(), endPointResource.getPathText());
                        endPointNode.put(PropertyName.uriTemplate.name(),
                                endPointResource.getUriTemplate().getUriTemplateString());

                        linkTemplateNode.put(PropertyName.endpoint.name(), endPointNode);
                    }

                } else {
                    responseSchemaUri = linkProtoSlot.getResponseSchemaUri();
                    requestSchemaUri = linkProtoSlot.getRequestSchemaUri();
                }

                if (responseSchemaUri != null) {
                    final ObjectNode responseSchemaNode = getSchemaNode(objectMapper, schemaNodes,
                            responseSchemaUri, schemaLoader);
                    linkTemplateNode.put(PropertyName.responseSchema.name(), responseSchemaNode);
                }

                if (requestSchemaUri != null) {
                    final ObjectNode requestSchemaNode = getSchemaNode(objectMapper, schemaNodes, requestSchemaUri,
                            schemaLoader);
                    linkTemplateNode.put(PropertyName.requestSchema.name(), requestSchemaNode);
                }

                final String signature = buildLinkSignature(linkSlotName, responseSchemaUri, requestSchemaUri,
                        schemaUri);
                linkTemplateNode.put(PropertyName.signature.name(), signature);
            }

            if (linkTemplatesNode.size() > 0) {
                final ObjectNode schemaNode = objectMapper.createObjectNode();
                final ObjectNode schemaDetailsNode = getSchemaNode(objectMapper, schemaNodes, schemaUri,
                        schemaLoader);
                schemaNode.put(PropertyName.schema.name(), schemaDetailsNode);
                schemaNode.put(PropertyName.linkTemplates.name(), linkTemplatesNode);
                linksNode.put(prototype.getUniqueName().getLocalName(), schemaNode);
            }
        }

        return linksNode;
    }

    private String buildLinkSignature(final String linkFunctionName, final URI responseSchemaUri,
            final URI requestSchemaUri, final URI thisSchemaUri) {

        final Context context = getContext();
        final SchemaLoader schemaLoader = context.getSchemaLoader();

        final StringBuilder signatureBuilder = new StringBuilder();

        if (responseSchemaUri != null) {
            final Prototype responsePrototype = schemaLoader.getPrototype(responseSchemaUri);
            final String responseSchemaName = responsePrototype.getUniqueName().getLocalName();
            signatureBuilder.append(responseSchemaName);
        } else {
            signatureBuilder.append("void");
        }

        signatureBuilder.append(" ");

        signatureBuilder.append(linkFunctionName).append(" ( ");

        if (requestSchemaUri != null && !requestSchemaUri.equals(thisSchemaUri)) {
            final Prototype requestPrototype = schemaLoader.getPrototype(requestSchemaUri);
            final String requestSchemaName = requestPrototype.getUniqueName().getLocalName();

            signatureBuilder.append(requestSchemaName);

            signatureBuilder.append(" ");

            final String parameterName = Character.toLowerCase(requestSchemaName.charAt(0))
                    + requestSchemaName.substring(1);
            signatureBuilder.append(parameterName);
        }

        signatureBuilder.append(" ) ");

        final String signature = signatureBuilder.toString();
        return signature;
    }

    private ObjectNode buildLinkRelationNode(final ObjectMapper objectMapper, final LinkRelation linkRelation) {

        final Context context = getContext();
        final SchemaLoader schemaLoader = context.getSchemaLoader();
        final SyntaxLoader syntaxLoader = context.getSyntaxLoader();

        final ObjectNode linkRelationNode = objectMapper.createObjectNode();

        linkRelationNode.put(PropertyName.uri.name(), syntaxLoader.formatSyntaxValue(linkRelation.getUri()));

        final UniqueName linkRelationUniqueName = linkRelation.getUniqueName();
        linkRelationNode.put(PropertyName.uniqueName.name(),
                syntaxLoader.formatSyntaxValue(linkRelationUniqueName));
        linkRelationNode.put(PropertyName.title.name(), linkRelation.getTitle());

        final String description = linkRelation.getDescription();
        if (StringUtils.isNotBlank(description)) {
            linkRelationNode.put(PropertyName.description.name(), description);
        }

        linkRelationNode.put(PropertyName.version.name(), linkRelation.getVersion());
        linkRelationNode.put(PropertyName.method.name(), linkRelation.getMethod().getProtocolGivenName());

        final URI responseSchemaUri = linkRelation.getResponseSchemaUri();
        if (responseSchemaUri != null) {
            final ObjectNode responseSchemaNode = SchemaDesignFormatter.buildSchemaNode(objectMapper,
                    responseSchemaUri, schemaLoader, null);
            linkRelationNode.put(PropertyName.responseSchema.name(), responseSchemaNode);
        }

        final URI requestSchemaUri = linkRelation.getRequestSchemaUri();
        if (requestSchemaUri != null) {
            final ObjectNode requestSchemaNode = SchemaDesignFormatter.buildSchemaNode(objectMapper,
                    requestSchemaUri, schemaLoader, null);
            linkRelationNode.put(PropertyName.requestSchema.name(), requestSchemaNode);
        }

        final String linkRelationName = linkRelationUniqueName.getLocalName();
        final String signature = buildLinkSignature(linkRelationName, responseSchemaUri, requestSchemaUri, null);
        linkRelationNode.put(PropertyName.signature.name(), signature);

        return linkRelationNode;
    }

    private LinkRelation getLinkRelation(final Map<URI, LinkRelation> linkRelationCache,
            final URI linkRelationUri) {

        final Context context = getContext();
        final SchemaLoader schemaLoader = context.getSchemaLoader();

        final LinkRelation linkRelation;
        if (linkRelationCache.containsKey(linkRelationUri)) {
            linkRelation = linkRelationCache.get(linkRelationUri);
        } else {
            final Keys linkRelationKeys = context.getApiLoader().buildDocumentKeys(linkRelationUri,
                    schemaLoader.getLinkRelationSchemaUri());
            linkRelation = context.getModel(linkRelationKeys, schemaLoader.getLinkRelationDimensions());
            linkRelationCache.put(linkRelationUri, linkRelation);
        }

        return linkRelation;
    }

    protected ObjectNode getSchemaNode(final ObjectMapper objectMapper, final Map<URI, ObjectNode> schemaNodes,
            final URI schemaUri, final SchemaLoader schemaLoader) {

        final ObjectNode schemaNode;
        if (schemaNodes.containsKey(schemaUri)) {
            schemaNode = schemaNodes.get(schemaUri);
        } else {
            schemaNode = SchemaDesignFormatter.buildSchemaNode(objectMapper, schemaUri, schemaLoader, null);
            schemaNodes.put(schemaUri, schemaNode);
        }

        return schemaNode;
    }

    @Override
    protected void initFromConfiguration(final FormatterConfiguration config) {

        final Map<String, String> settings = config.getSettings();
        if (settings == null) {
            throw new NullPointerException("The settings cannot be null.");
        }

        _Templates = new HashMap<>();

        try {
            getTemplate(SHELL_PAGE_TEMPLATE_RESOURCE);
        } catch (IOException e) {
            throw new IllegalArgumentException(
                    "The shell page template could not be read from: " + SHELL_PAGE_TEMPLATE_RESOURCE);
        }

        _Docroot = DEFAULT_DOCROOT;
        if (settings.containsKey(DOCROOT_SETTING_NAME)) {
            _Docroot = settings.get(DOCROOT_SETTING_NAME);
        }

        if (settings.containsKey(MINIFY_SETTING_NAME)) {
            _IsSourceCodeMinified = Boolean.valueOf(settings.get(MINIFY_SETTING_NAME));
        }

    }

    private enum PropertyName {
        allResources, defaultSchema, description, endpoint, fullPath, id, links, linkTemplates, method, parentPath, pathSegment, references, rel, relationTitle, requestSchema, resource, responseSchema, schema, signature, title, uniqueName, uri, uriTemplate, version;

    }

    private enum LinkTemplateType {
        Reference, Link;
    }
}