View Javadoc
1   /*
2    * Copyright (c) 2011-2024 PrimeFaces Extensions
3    *
4    *  Permission is hereby granted, free of charge, to any person obtaining a copy
5    *  of this software and associated documentation files (the "Software"), to deal
6    *  in the Software without restriction, including without limitation the rights
7    *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    *  copies of the Software, and to permit persons to whom the Software is
9    *  furnished to do so, subject to the following conditions:
10   *
11   *  The above copyright notice and this permission notice shall be included in
12   *  all copies or substantial portions of the Software.
13   *
14   *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20   *  THE SOFTWARE.
21   */
22  package org.primefaces.extensions.component.localized;
23  
24  import java.io.IOException;
25  import java.net.URISyntaxException;
26  import java.net.URL;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.nio.file.Paths;
30  import java.util.Locale;
31  
32  import javax.el.ExpressionFactory;
33  import javax.faces.FacesException;
34  import javax.faces.component.UIComponent;
35  import javax.faces.context.FacesContext;
36  import javax.servlet.ServletContext;
37  
38  import org.primefaces.extensions.config.PrimeExtensionsEnvironment;
39  import org.primefaces.extensions.util.CommonMarkWrapper;
40  import org.primefaces.renderkit.CoreRenderer;
41  import org.primefaces.util.EscapeUtils;
42  import org.primefaces.util.FacetUtils;
43  import org.primefaces.util.LangUtils;
44  
45  /**
46   * Renderer for the {@link Localized} component.
47   *
48   * @author Jasper de Vries <jepsar@gmail.com>
49   * @since 11.0.3
50   */
51  public class LocalizedRenderer extends CoreRenderer {
52  
53      public static final String WEB_FOLDER = "WEB-INF/pfe-localized";
54      public static final String QUARKUS_FOLDER = "pfe-localized";
55  
56      @Override
57      public void encodeEnd(final FacesContext context, final UIComponent component) throws IOException {
58          final Localized localized = (Localized) component;
59          encodeMarkup(context, localized);
60      }
61  
62      protected void encodeMarkup(final FacesContext context, final Localized localized) throws IOException {
63          if (LangUtils.isBlank(localized.getName())) {
64              encodeFromFacet(context, localized);
65          }
66          else {
67              encodeFromFile(context, localized);
68          }
69      }
70  
71      protected void encodeFromFacet(final FacesContext context, final Localized localized) throws IOException {
72          final Locale locale = localized.calculateLocale(context);
73          final String language = locale.getLanguage();
74          final String country = locale.getCountry();
75          resolveFacet(localized, language, country).encodeAll(context);
76      }
77  
78      protected UIComponent resolveFacet(final Localized localized, final String language, final String country) {
79          UIComponent facet = localized.getFacet(language + "_" + country);
80          if (FacetUtils.shouldRenderFacet(facet)) {
81              return facet;
82          }
83          facet = localized.getFacet(language);
84          if (FacetUtils.shouldRenderFacet(facet)) {
85              return facet;
86          }
87          facet = localized.getFacet("default");
88          if (FacetUtils.shouldRenderFacet(facet)) {
89              return facet;
90          }
91          throw new FacesException("No facet found for " + language + "_" + country + ", nor a 'default' facet");
92      }
93  
94      protected void encodeFromFile(final FacesContext context, final Localized localized) throws IOException {
95          final Path filePath = resolvePath(context, localized);
96          final byte[] bytes = Files.readAllBytes(filePath);
97          String value = new String(bytes);
98          if (localized.isEvalEl()) {
99              value = evaluateEl(context, value);
100         }
101         if (localized.isEscape()) {
102             value = EscapeUtils.forHtml(value);
103         }
104         if (localized.isMarkdown()) {
105             value = toHTML(context, value);
106         }
107         context.getResponseWriter().append(value);
108     }
109 
110     protected Path resolvePath(final FacesContext context, final Localized localized) {
111         final ServletContext servletContext = ((ServletContext) context.getExternalContext().getContext());
112         final String web = servletContext.getRealPath(WEB_FOLDER);
113         final String meta = servletContext.getRealPath(QUARKUS_FOLDER);
114         final Locale locale = localized.calculateLocale(context);
115         final String language = locale.getLanguage();
116         final String country = locale.getCountry();
117         final String folder = localized.getFolder();
118         final String name = localized.getName();
119         Path path = resolvePath(web, folder, name, language, country);
120         if (path == null) {
121             path = resolvePath(web, null, name, language, country);
122         }
123         if (path == null) {
124             path = resolvePath(meta, folder, name, language, country);
125         }
126         if (path == null) {
127             path = resolvePath(meta, null, name, language, country);
128         }
129         if (path == null) {
130             throw new IllegalStateException("Cannot find Localized file for: " + localized.getClientId(context));
131         }
132         return path;
133     }
134 
135     protected Path resolvePath(final String base, final String folder, final String name, final String language,
136                 final String country) {
137         final String baseFolder = LangUtils.isBlank(folder)
138                     ? base
139                     : base + "/" + folder;
140         Path path = existingPath(baseFolder, name + "_" + language + "_" + country);
141         if (path == null) {
142             path = existingPath(baseFolder, name + "_" + language);
143         }
144         if (path == null) {
145             path = existingPath(baseFolder, name);
146         }
147         return path;
148     }
149 
150     protected Path existingPath(final String first, final String more) {
151         final Path path = Paths.get(first, more);
152         Path existingPath = path.toFile().exists() ? path : null;
153         if (existingPath == null) {
154             final String resourcePath = first + "/" + more;
155             try {
156                 // Quarkus
157                 final URL url = Thread.currentThread().getContextClassLoader().getResource(resourcePath);
158                 if (url != null) {
159                     existingPath = Paths.get(url.toURI());
160                 }
161             }
162             catch (final URISyntaxException e) {
163                 throw new RuntimeException(e);
164             }
165         }
166         return existingPath;
167     }
168 
169     protected String toHTML(final FacesContext context, final String value) {
170         if (!PrimeExtensionsEnvironment.getCurrentInstance(context).isCommonmarkAvailable()) {
171             throw new FacesException("CommonMark not available.");
172         }
173         return CommonMarkWrapper.toHTML(value);
174     }
175 
176     protected String evaluateEl(final FacesContext context, final String value) {
177         return (String) ExpressionFactory.newInstance()
178                     .createValueExpression(context.getELContext(), value, String.class)
179                     .getValue(context.getELContext());
180     }
181 }