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