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