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