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 java.util.HashMap;
020import java.util.Map;
021import java.util.Objects;
022import java.util.Set;
023
024import com.fasterxml.jackson.annotation.JsonAnySetter;
025import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
026import org.forgerock.api.ApiValidationException;
027import org.forgerock.http.routing.Version;
028import org.forgerock.util.Reject;
029
030import com.fasterxml.jackson.annotation.JsonIgnore;
031import com.fasterxml.jackson.annotation.JsonValue;
032
033/**
034 * Class that represents versioned {@link Resource}s on an API descriptor path.
035 */
036@JsonDeserialize(builder = VersionedPath.Builder.class)
037public final class VersionedPath {
038
039    /**
040     * Version {@code 0.0} represents null/empty, for when resource versions are not required by an API (e.g., OpenIDM).
041     */
042    public static final Version UNVERSIONED = Version.version(0);
043
044    private final Map<Version, Resource> paths;
045
046    private VersionedPath(Builder builder) {
047        this.paths = builder.paths;
048
049        if (paths.isEmpty()) {
050            throw new ApiValidationException("Must have at least one versioned resource");
051        }
052        if (paths.size() > 1) {
053            for (final Version version : paths.keySet()) {
054                if (UNVERSIONED.equals(version)) {
055                    throw new ApiValidationException("Version 0.0 (unversioned) must be the only version when used");
056                }
057            }
058        }
059    }
060
061    /**
062     * Gets a {@code Map} of versions to {@link Resource}s. This method is currently only used for JSON serialization.
063     *
064     * @return {@code Map} of versions to {@link Resource}s.
065     */
066    @JsonValue
067    protected Map<Version, Resource> getPaths() {
068        return paths;
069    }
070
071    /**
072     * Gets the {@link Resource} for a given version.
073     *
074     * @param version Resource version
075     * @return {@link Resource} or {@code null} if does-not-exist.
076     */
077    @JsonIgnore
078    public Resource get(Version version) {
079        return paths.get(version);
080    }
081
082    /**
083     * Returns all resource-versions on this path.
084     *
085     * @return All resource-versions.
086     */
087    @JsonIgnore
088    public Set<Version> getVersions() {
089        return paths.keySet();
090    }
091
092    /**
093     * Create a new Builder for VersionedPath.
094     *
095     * @return Builder
096     */
097    public static Builder versionedPath() {
098        return new Builder();
099    }
100
101    /**
102     * Allows for mutation of paths when merging {@code Paths} instances.
103     * @param v The version.
104     * @param resource The resource.
105     */
106    void addVersion(Version v, Resource resource) {
107        if (paths.containsKey(v)) {
108            throw new IllegalArgumentException("Trying to redefine version: " + v);
109        }
110        paths.put(v, Reject.checkNotNull(resource));
111    }
112
113    @Override
114    public boolean equals(Object o) {
115        if (this == o) {
116            return true;
117        }
118        if (o == null || getClass() != o.getClass()) {
119            return false;
120        }
121        VersionedPath that = (VersionedPath) o;
122        return Objects.equals(paths, that.paths);
123    }
124
125    @Override
126    public int hashCode() {
127        return Objects.hash(paths);
128    }
129
130    /**
131     * Builder to help construct the VersionedPath.
132     */
133    public static final class Builder {
134
135        private final Map<Version, Resource> paths = new HashMap<>();
136
137        /**
138         * Private default constructor.
139         */
140        private Builder() {
141        }
142
143        /**
144         * Adds a resource-version.
145         *
146         * @param version Resource-version
147         * @param resource {@link Resource}
148         * @return Builder
149         */
150        public Builder put(Version version, Resource resource) {
151            if (paths.containsKey(version)) {
152                throw new IllegalStateException("version not unique");
153            }
154            paths.put(Reject.checkNotNull(version), Reject.checkNotNull(resource));
155            return this;
156        }
157
158        /**
159         * Adds a resource-version.
160         *
161         * @param version Resource-version as string
162         * @param resource {@link Resource}
163         * @return Builder
164         */
165        @JsonAnySetter
166        public Builder put(String version, Resource resource) {
167            return put(Version.version(version), resource);
168        }
169
170        /**
171         * Builds the VersionedPath instance.
172         *
173         * @return VersionedPath instance
174         */
175        public VersionedPath build() {
176            return new VersionedPath(this);
177        }
178    }
179
180}