View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2019 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.wcm.ui.granite.util;
21  
22  import java.util.HashMap;
23  import java.util.Map;
24  
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.commons.lang3.Strings;
27  import org.apache.sling.api.SlingHttpServletRequest;
28  import org.apache.sling.api.resource.ResourceResolver;
29  import org.jetbrains.annotations.NotNull;
30  import org.osgi.annotation.versioning.ProviderType;
31  
32  import com.adobe.granite.ui.components.ComponentHelper;
33  import com.adobe.granite.ui.components.Config;
34  import com.adobe.granite.ui.components.ExpressionHelper;
35  import com.day.text.Text;
36  
37  /**
38   * Helper class for path-based GraniteUI components to resolve the root path.
39   *
40   * <p>
41   * Resolution order for root path detection:
42   * </p>
43   * <ul>
44   * <li>Reads configured root path from <code>rootPath</code> property</li>
45   * <li>Calls the provided root path detector implementation to detect root path from context</li>
46   * <li>Reads fallback root path from <code>fallbackRootPath</code> property</li>
47   * <li>Uses fallback root path provided for this instance</li>
48   * <li>Fallback to "/"</li>
49   * </ul>
50   *
51   * <p>
52   * Additionally the root path is modified:
53   * </p>
54   * <ul>
55   * <li>If an <code>appendPath</code> property is configured it is appended to the detected root path</li>
56   * <li>Than it is checked if the root path is valid - if not the next-valid parent path is returned</li>
57   * </ul>
58   */
59  @ProviderType
60  public final class RootPathResolver {
61  
62    static final String PN_ROOT_PATH = "rootPath";
63    static final String PN_APPEND_PATH = "appendPath";
64    static final String PN_FALLBACK_PATH = "fallbackRootPath";
65    static final String DEFAULT_FALLBACK_ROOT_PATH = "/";
66  
67    private final ComponentHelper cmp;
68    private final Config cfg;
69    private final ExpressionHelper ex;
70    private final SlingHttpServletRequest request;
71    private final ResourceResolver resourceResolver;
72  
73    private RootPathDetector rootPathDetector;
74    private String fallbackRootPath = DEFAULT_FALLBACK_ROOT_PATH;
75  
76    /**
77     * Creates a new RootPathResolver instance.
78     * @param cmp Component helper
79     * @param request Request
80     */
81    public RootPathResolver(@NotNull ComponentHelper cmp, @NotNull SlingHttpServletRequest request) {
82      this.cmp = cmp;
83      this.cfg = cmp.getConfig();
84      this.ex = cmp.getExpressionHelper();
85      this.request = request;
86      this.resourceResolver = request.getResourceResolver();
87    }
88  
89    /**
90     * Sets the root path detector implementation to detect root path from context.
91     * @param rootPathDetector For detecting root path from context
92     */
93    public void setRootPathDetector(@NotNull RootPathDetector rootPathDetector) {
94      this.rootPathDetector = rootPathDetector;
95    }
96  
97    /**
98     * Sets the fallback root path to be used if none is configured.
99     * @param fallbackRootPath Fallback root path that is used if none is configured
100    */
101   public void setFallbackRootPath(@NotNull String fallbackRootPath) {
102     this.fallbackRootPath = fallbackRootPath;
103   }
104 
105   /**
106    * Get the resolved and validated root path.
107    * @return Root path.
108    */
109   public @NotNull String get() {
110     // get configured or detected or fallback root path
111     String rootPath = getRootPath();
112 
113     // append path if configured
114     rootPath = appendPath(rootPath);
115 
116     // resolve to existing path
117     return getExistingPath(rootPath);
118   }
119 
120   /**
121    * Get map of override properties for super component based on the wcm.io Granite UI Extensions PathField.
122    * @return Path properties
123    */
124   public Map<String, Object> getOverrideProperties() {
125     Map<String, Object> props = new HashMap<>();
126     props.put(PN_ROOT_PATH, get());
127     props.put(PN_APPEND_PATH, "");
128     props.put(PN_FALLBACK_PATH, "");
129     return props;
130   }
131 
132   /**
133    * @return Configured or detected root path or fallback path
134    */
135   private @NotNull String getRootPath() {
136 
137     // check for configured root path
138     String rootPath = ex.getString(cfg.get(PN_ROOT_PATH, String.class));
139     if (StringUtils.isNotBlank(rootPath)) {
140       return rootPath;
141     }
142 
143     // call root path detector
144     if (rootPathDetector != null) {
145       rootPath = rootPathDetector.detectRootPath(cmp, request);
146       if (rootPath != null && StringUtils.isNotBlank(rootPath)) {
147         return rootPath;
148       }
149     }
150 
151     // check for configured fallback path
152     rootPath = ex.getString(cfg.get(PN_FALLBACK_PATH, String.class));
153     if (StringUtils.isNotBlank(rootPath)) {
154       return rootPath;
155     }
156 
157     // fallback to default fallback path
158     return fallbackRootPath;
159   }
160 
161   /**
162    * Appends the "appendPath" if configured.
163    * @param rootPath Root path
164    * @return Path with appendix
165    */
166   private @NotNull String appendPath(@NotNull String rootPath) {
167     String appendPath = ex.getString(cfg.get(PN_APPEND_PATH, String.class));
168     if (StringUtils.isBlank(appendPath)) {
169       return rootPath;
170     }
171     StringBuilder combinedPath = new StringBuilder(rootPath);
172     if (!Strings.CS.startsWith(appendPath, "/")) {
173       combinedPath.append("/");
174     }
175     combinedPath.append(appendPath);
176     return combinedPath.toString();
177   }
178 
179   /**
180    * Make sure the root path exists. If it does not exist go up to parent hierarchy until it returns an
181    * existing resource path.
182    */
183   @NotNull
184   String getExistingPath(@NotNull String rootPath) {
185     if (resourceResolver.getResource(rootPath) == null) {
186       String parentPath = Text.getRelativeParent(rootPath, 1);
187       if (StringUtils.isBlank(parentPath)) {
188         return DEFAULT_FALLBACK_ROOT_PATH;
189       }
190       return getExistingPath(parentPath);
191     }
192     else {
193       return rootPath;
194     }
195   }
196 
197 }