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