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