001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.microservice.resources;
014
015import static org.apache.juneau.dto.html5.HtmlBuilder.*;
016import static org.apache.juneau.http.HttpMethod.*;
017
018import java.io.*;
019import java.util.Map;
020
021import org.apache.juneau.collections.*;
022import org.apache.juneau.dto.html5.*;
023import org.apache.juneau.html.annotation.HtmlDocConfig;
024import org.apache.juneau.http.annotation.Body;
025import org.apache.juneau.http.annotation.FormData;
026import org.apache.juneau.http.annotation.Path;
027import org.apache.juneau.http.annotation.Response;
028import org.apache.juneau.parser.*;
029import org.apache.juneau.rest.*;
030import org.apache.juneau.rest.annotation.*;
031import org.apache.juneau.http.exception.*;
032
033/**
034 * Shows contents of the microservice configuration file.
035 */
036@Rest(
037   path="/config",
038   title="Configuration",
039   description="Contents of configuration file."
040)
041@HtmlDocConfig(
042   navlinks={
043      "up: request:/..",
044      "options: servlet:/?method=OPTIONS",
045      "stats: servlet:/stats",
046      "edit: servlet:/edit"
047   }
048)
049@SuppressWarnings("javadoc")
050public class ConfigResource extends BasicRestServlet {
051   private static final long serialVersionUID = 1L;
052
053   @RestMethod(
054      name=GET,
055      path="/",
056      summary="Get config file contents",
057      description="Show contents of config file as an OMap.",
058      swagger=@MethodSwagger(
059         responses={
060            "200:{ description:'Config file as a map of map of objects.', 'x-example':{'':{defaultKey:'defaultValue'},'Section1':{key1:'val1',key2:123}}}"
061         }
062      )
063   )
064   public OMap getConfig() {
065      return getServletConfig().getConfig().toMap();
066   }
067
068   @RestMethod(
069      name=GET,
070      path="/edit",
071      summary="Render form entry page for editing config file",
072      description="Renders a form entry page for editing the raw text of a config file."
073   )
074   public Form getConfigEditForm() {
075      return form().id("form").action("servlet:/").method("POST").enctype("application/x-www-form-urlencoded").children(
076         div()._class("data").children(
077            table(
078               tr(td().style("text-align:right").children(button("submit","Submit"),button("reset","Reset"))),
079               tr(th().child("Contents")),
080               tr(th().child(
081                  textarea().name("contents").rows(40).cols(120).style("white-space:pre;word-wrap:normal;overflow-x:scroll;font-family:monospace;")
082                     .text(getServletConfig().getConfig().toString()))
083               )
084            )
085         )
086      );
087   }
088
089   @RestMethod(
090      name=GET,
091      path="/{section}",
092      summary="Get config file section contents",
093      description="Show contents of config file section as an OMap.",
094      swagger=@MethodSwagger(
095         responses={
096            "200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}"
097         }
098      )
099   )
100   public OMap getConfigSection(
101         @Path(n="section", d="Section name in config file.", ex="REST") String section
102      ) throws SectionNotFound, BadConfig {
103
104      return getSection(section);
105   }
106
107   @RestMethod(
108      name=GET,
109      path="/{section}/{key}",
110      summary="Get config file entry value",
111      description="Show value of config file entry as a simple string.",
112      swagger=@MethodSwagger(
113         responses={
114            "200:{ description:'Entry value.', 'x-example':'servlet:/htdocs/themes/dark.css'}"
115         }
116      )
117   )
118   public String getConfigEntry(
119         @Path(n="section", d="Section name in config file.", ex="REST") String section,
120         @Path(n="key", d="Key name in section.", ex="theme") String key
121      ) throws SectionNotFound, BadConfig {
122
123      return getSection(section).getString(key);
124   }
125
126   @RestMethod(
127      name=POST,
128      path="/",
129      summary="Update config file contents",
130      description="Update the contents of the config file from a FORM post.",
131      swagger=@MethodSwagger(
132         responses={
133            "200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}"
134         }
135      )
136   )
137   public OMap setConfigContentsFormPost(
138         @FormData(n="contents", d="New contents in INI file format.") String contents
139      ) throws Exception {
140
141      return setConfigContents(new StringReader(contents));
142   }
143
144   @RestMethod(
145      name=PUT,
146      path="/",
147      summary="Update config file contents",
148      description="Update the contents of the config file from raw text.",
149      swagger=@MethodSwagger(
150         responses={
151            "200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}"
152         }
153      )
154   )
155   public OMap setConfigContents(
156         @Body(d="New contents in INI file format.") Reader contents
157      ) throws Exception {
158
159      return getServletConfig().getConfig().load(contents, true).toMap();
160   }
161
162   @RestMethod(
163      name=PUT,
164      path="/{section}",
165      summary="Update config section contents",
166      description="Add or overwrite a config file section.",
167      swagger=@MethodSwagger(
168         responses={
169            "200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}"
170         }
171      )
172   )
173   public OMap setConfigSection(
174         @Path(n="section", d="Section name in config file.", ex="REST") String section,
175         @Body(
176            d="New contents of config section as a simple map of key/value pairs.",
177            ex="{theme:'servlet:/htdocs/themes/dark.css'}"
178         ) Map<String,Object> contents
179      ) throws Exception {
180
181      getServletConfig().getConfig().setSection(section, null, contents);
182      return getSection(section);
183   }
184
185   @RestMethod(
186      name=PUT,
187      path="/{section}/{key}",
188      summary="Update config entry value",
189      description="Add or overwrite a config file entry.",
190      swagger=@MethodSwagger(
191         responses={
192            "200:{ description:'The updated value.', 'x-example':'servlet:/htdocs/themes/dark.css'}"
193         }
194      )
195   )
196   public String setConfigValue(
197         @Path(n="section", d="Section name in config file.", ex="REST") String section,
198         @Path(n="key", d="Key name in section.", ex="theme") String key,
199         @Body(d="New value for entry.", ex="servlet:/htdocs/themes/dark.css") String value
200      ) throws SectionNotFound, BadConfig {
201
202      getServletConfig().getConfig().set(section + '/' + key, value);
203      return getSection(section).getString(key);
204   }
205
206   //-----------------------------------------------------------------------------------------------------------------
207   // Helper beans
208   //-----------------------------------------------------------------------------------------------------------------
209
210   @Response(description="Section not found.")
211   private class SectionNotFound extends NotFound {
212      private static final long serialVersionUID = 1L;
213
214      SectionNotFound() {
215         super("Section not found.");
216      }
217   }
218
219   @Response(description="The configuration file contained syntax errors and could not be parsed.")
220   private class BadConfig extends InternalServerError {
221      private static final long serialVersionUID = 1L;
222
223      BadConfig(Exception e) {
224         super(e, "The configuration file contained syntax errors and could not be parsed.");
225      }
226   }
227
228   //-----------------------------------------------------------------------------------------------------------------
229   // Helper methods
230   //-----------------------------------------------------------------------------------------------------------------
231
232   private OMap getSection(String name) throws SectionNotFound, BadConfig {
233      OMap m;
234      try {
235         m = getServletConfig().getConfig().getSectionAsMap(name);
236      } catch (ParseException e) {
237         throw new BadConfig(e);
238      }
239      if (m == null)
240         throw new SectionNotFound();
241      return m;
242   }
243}