001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2016 ForgeRock AS.
015 */
016
017package org.forgerock.api.models;
018
019import static org.forgerock.api.util.ValidationUtil.*;
020
021import java.util.HashMap;
022import java.util.Map;
023import java.util.Objects;
024import java.util.Set;
025
026import com.fasterxml.jackson.annotation.JsonAnySetter;
027import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
028import org.forgerock.api.util.PathUtil;
029import org.forgerock.http.routing.Version;
030import org.forgerock.util.Reject;
031
032import com.fasterxml.jackson.annotation.JsonIgnore;
033import com.fasterxml.jackson.annotation.JsonValue;
034
035/**
036 * Class that represents the Paths type in API descriptor.
037 */
038@JsonDeserialize(builder = Paths.Builder.class)
039public final class Paths {
040
041    private final Map<String, VersionedPath> paths;
042
043    private Paths(Builder builder) {
044        this.paths = builder.paths;
045    }
046
047    /**
048     * Gets a {@code Map} of path-names to Paths. This method is currently only used for JSON serialization.
049     *
050     * @return {@code Map} of path-names to Paths.
051     */
052    @JsonValue
053    protected Map<String, VersionedPath> getPaths() {
054        return paths;
055    }
056
057    /**
058     * Gets the Path for a given Path-name.
059     *
060     * @param name Path name
061     * @return Path or {@code null} if does-not-exist.
062     */
063    @JsonIgnore
064    public VersionedPath get(String name) {
065        return paths.get(name);
066    }
067
068    /**
069     * Returns all Path names.
070     *
071     * @return All Path names.
072     */
073    @JsonIgnore
074    public Set<String> getNames() {
075        return paths.keySet();
076    }
077
078    /**
079     * Create a new Builder for Paths.
080     *
081     * @return Builder
082     */
083    public static Builder paths() {
084        return new Builder();
085    }
086
087    @Override
088    public boolean equals(Object o) {
089        if (this == o) {
090            return true;
091        }
092        if (o == null || getClass() != o.getClass()) {
093            return false;
094        }
095        Paths paths1 = (Paths) o;
096        return Objects.equals(paths, paths1.paths);
097    }
098
099    @Override
100    public int hashCode() {
101        return Objects.hash(paths);
102    }
103
104    /**
105     * Builder to help construct the Paths.
106     */
107    public static final class Builder {
108
109        private final Map<String, VersionedPath> paths = new HashMap<>();
110
111        /**
112         * Private default constructor.
113         */
114        private Builder() {
115        }
116
117        /**
118         * Adds a Path.
119         *
120         * @param path Path string
121         * @param versionedPath Versioned path
122         * @return Builder
123         */
124        @JsonAnySetter
125        public Builder put(String path, VersionedPath versionedPath) {
126            if (path == null || containsWhitespace(path)) {
127                throw new IllegalArgumentException("path required and may not contain whitespace");
128            }
129            if (!path.isEmpty()) {
130                // paths must start with a slash (OpenAPI spec) and not end with one
131                path = PathUtil.buildPath(path);
132            }
133            if (paths.containsKey(path)) {
134                throw new IllegalStateException("path not unique");
135            }
136            paths.put(path, Reject.checkNotNull(versionedPath));
137            return this;
138        }
139
140        /**
141         * Merge the path definition into the existing path definitions. If there is already a {@code VersionedPath}
142         * at this path, then the versions will be added together.
143         *
144         * @param path Path string
145         * @param versionedPath Versioned path
146         * @return Builder.
147         */
148        public Builder merge(String path, VersionedPath versionedPath) {
149            if (path == null || containsWhitespace(path)) {
150                throw new IllegalArgumentException("path required and may not contain whitespace");
151            }
152            if (!path.isEmpty()) {
153                // paths must start with a slash (OpenAPI spec) and not end with one
154                path = PathUtil.buildPath(path);
155            }
156            if (!paths.containsKey(path)) {
157                put(path, Reject.checkNotNull(versionedPath));
158            } else {
159                VersionedPath existing = paths.get(path);
160                for (Version v : versionedPath.getVersions()) {
161                    existing.addVersion(v, versionedPath.get(v));
162                }
163            }
164            return this;
165        }
166
167        /**
168         * Builds the Paths instance.
169         *
170         * @return Paths instance
171         */
172        public Paths build() {
173            return new Paths(this);
174        }
175    }
176
177}