001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.oozie.servlet;
019
020import java.io.IOException;
021import java.io.Reader;
022import java.util.List;
023import java.util.Map;
024import java.util.TimeZone;
025import java.util.regex.Pattern;
026
027import javax.servlet.ServletException;
028import javax.servlet.http.HttpServletRequest;
029import javax.servlet.http.HttpServletResponse;
030
031import org.apache.hadoop.fs.Path;
032import org.apache.oozie.BuildInfo;
033import org.apache.oozie.client.rest.JsonBean;
034import org.apache.oozie.client.rest.JsonTags;
035import org.apache.oozie.client.rest.RestConstants;
036import org.apache.oozie.service.AuthorizationException;
037import org.apache.oozie.service.AuthorizationService;
038import org.apache.oozie.service.InstrumentationService;
039import org.apache.oozie.service.JobsConcurrencyService;
040import org.apache.oozie.service.Services;
041import org.apache.oozie.service.ShareLibService;
042import org.apache.oozie.util.AuthUrlClient;
043import org.apache.oozie.util.ConfigUtils;
044import org.apache.oozie.util.Instrumentation;
045import org.json.simple.JSONArray;
046import org.json.simple.JSONObject;
047import org.json.simple.JSONValue;
048
049public abstract class BaseAdminServlet extends JsonRestServlet {
050
051    private static final long serialVersionUID = 1L;
052    protected String modeTag;
053
054
055    public BaseAdminServlet(String instrumentationName, ResourceInfo[] RESOURCES_INFO) {
056        super(instrumentationName, RESOURCES_INFO);
057        setAllowSafeModeChanges(true);
058    }
059
060    /**
061     * Change safemode state.
062     */
063    @Override
064    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
065        String resourceName = getResourceName(request);
066        request.setAttribute(AUDIT_OPERATION, resourceName);
067        request.setAttribute(AUDIT_PARAM, request.getParameter(modeTag));
068
069        authorizeRequest(request);
070
071        setOozieMode(request, response, resourceName);
072        /*if (resourceName.equals(RestConstants.ADMIN_STATUS_RESOURCE)) {
073            boolean safeMode = Boolean.parseBoolean(request.getParameter(RestConstants.ADMIN_SAFE_MODE_PARAM));
074            Services.get().setSafeMode(safeMode);
075            response.setStatus(HttpServletResponse.SC_OK);
076        }
077        else {
078            throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0301, resourceName);
079        }*/
080    }
081
082
083    /**
084     * Get JMS connection Info
085     * @param request
086     * @param response
087     * @throws XServletException
088     * @throws IOException
089     */
090    abstract JsonBean getJMSConnectionInfo(HttpServletRequest request, HttpServletResponse response)
091            throws XServletException, IOException;
092
093
094    /**
095     * Return safemode state, instrumentation, configuration, osEnv or
096     * javaSysProps
097     */
098    @Override
099    @SuppressWarnings("unchecked")
100    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
101        String resource = getResourceName(request);
102        Instrumentation instr = Services.get().get(InstrumentationService.class).get();
103
104        if (resource.equals(RestConstants.ADMIN_STATUS_RESOURCE)) {
105            JSONObject json = new JSONObject();
106            populateOozieMode(json);
107            // json.put(JsonTags.SYSTEM_SAFE_MODE, getOozeMode());
108            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
109        }
110        else if (resource.equals(RestConstants.ADMIN_OS_ENV_RESOURCE)) {
111            JSONObject json = new JSONObject();
112            json.putAll(instr.getOSEnv());
113            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
114        }
115        else if (resource.equals(RestConstants.ADMIN_JAVA_SYS_PROPS_RESOURCE)) {
116            JSONObject json = new JSONObject();
117            json.putAll(instr.getJavaSystemProperties());
118            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
119        }
120        else if (resource.equals(RestConstants.ADMIN_CONFIG_RESOURCE)) {
121            JSONObject json = new JSONObject();
122            json.putAll(instr.getConfiguration());
123            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
124        }
125        else if (resource.equals(RestConstants.ADMIN_INSTRUMENTATION_RESOURCE)) {
126            sendInstrumentationResponse(response, instr);
127        }
128        else if (resource.equals(RestConstants.ADMIN_BUILD_VERSION_RESOURCE)) {
129            JSONObject json = new JSONObject();
130            json.put(JsonTags.BUILD_VERSION, BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION));
131            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
132        }
133        else if (resource.equals(RestConstants.ADMIN_QUEUE_DUMP_RESOURCE)) {
134            JSONObject json = new JSONObject();
135            getQueueDump(json);
136            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
137        }
138        else if (resource.equals(RestConstants.ADMIN_TIME_ZONES_RESOURCE)) {
139            JSONObject json = new JSONObject();
140            json.put(JsonTags.AVAILABLE_TIME_ZONES, availableTimeZonesToJsonArray());
141            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
142        }
143        else if (resource.equals(RestConstants.ADMIN_JMS_INFO)) {
144            String timeZoneId = request.getParameter(RestConstants.TIME_ZONE_PARAM) == null ? "GMT" : request
145                    .getParameter(RestConstants.TIME_ZONE_PARAM);
146            JsonBean jmsBean = getJMSConnectionInfo(request, response);
147            sendJsonResponse(response, HttpServletResponse.SC_OK, jmsBean, timeZoneId);
148        }
149        else if (resource.equals(RestConstants.ADMIN_AVAILABLE_OOZIE_SERVERS_RESOURCE)) {
150            JSONObject json = new JSONObject();
151            json.putAll(getOozieURLs());
152            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
153        }
154        else if (resource.equals(RestConstants.ADMIN_UPDATE_SHARELIB)) {
155            authorizeRequest(request);
156            updateShareLib(request, response);
157        }
158        else if (resource.equals(RestConstants.ADMIN_LIST_SHARELIB)) {
159            String sharelibKey = request.getParameter(RestConstants.SHARE_LIB_REQUEST_KEY);
160            sendJsonResponse(response, HttpServletResponse.SC_OK, getShareLib(sharelibKey));
161        }
162        else if (resource.equals(RestConstants.ADMIN_METRICS_RESOURCE)) {
163            sendMetricsResponse(response);
164        }
165    }
166
167    /**
168     * Gets the list of share lib.
169     *
170     * @param sharelibKey the sharelib key
171     * @return the list of supported share lib
172     * @throws IOException
173     */
174    @SuppressWarnings("unchecked")
175    private JSONObject getShareLib(String sharelibKey) throws IOException {
176        JSONObject json = new JSONObject();
177
178        ShareLibService shareLibService = Services.get().get(ShareLibService.class);
179
180        // for testcases.
181        if (shareLibService == null) {
182            return json;
183        }
184        JSONArray shareLibList = new JSONArray();
185
186        Map<String, List<Path>> shareLibLauncherMap = shareLibService.getShareLib();
187        if (sharelibKey != null && !sharelibKey.isEmpty()) {
188            Pattern pattern = Pattern.compile(sharelibKey);
189            for (String key : shareLibLauncherMap.keySet()) {
190                if (pattern.matcher(key).matches() == true) {
191                    JSONObject object = new JSONObject();
192                    JSONArray fileList = new JSONArray();
193                    List<Path> pathList = shareLibLauncherMap.get(key);
194
195                    for (Path file : pathList) {
196                        fileList.add(file.toString());
197                    }
198                    object.put(JsonTags.SHARELIB_LIB_NAME, key);
199                    object.put(JsonTags.SHARELIB_LIB_FILES, fileList);
200                    shareLibList.add(object);
201
202                }
203            }
204        }
205        else {
206            for (String key : shareLibLauncherMap.keySet()) {
207                JSONObject object = new JSONObject();
208                object.put(JsonTags.SHARELIB_LIB_NAME, key);
209                shareLibList.add(object);
210            }
211
212        }
213        json.put(JsonTags.SHARELIB_LIB, shareLibList);
214
215        return json;
216    }
217
218    /**
219     * Update share lib. support HA
220     *
221     * @param request the request
222     * @param response the response
223     * @throws IOException Signals that an I/O exception has occurred.
224     */
225    @SuppressWarnings("unchecked")
226    public void updateShareLib(HttpServletRequest request, HttpServletResponse response) throws IOException {
227        JSONArray jsonArray = new JSONArray();
228        JobsConcurrencyService jc = Services.get().get(JobsConcurrencyService.class);
229        if (jc.isAllServerRequest(request.getParameterMap())) {
230            Map<String, String> servers = jc.getOtherServerUrls();
231            for (String otherUrl : servers.values()) {
232                // It's important that we specify ALL_SERVERS_PARAM=false, so that other oozie server should not call other oozie
233                //servers to update sharelib (and creating an infinite recursion)
234                String serverUrl = otherUrl + "/v2/admin/" + RestConstants.ADMIN_UPDATE_SHARELIB + "?"
235                        + RestConstants.ALL_SERVER_REQUEST + "=false";
236                try {
237                    Reader reader = AuthUrlClient.callServer(serverUrl);
238                    JSONObject json = (JSONObject) JSONValue.parse(reader);
239                    jsonArray.add(json);
240                }
241                catch (Exception e) {
242                    JSONObject errorJson = new JSONObject();
243                    errorJson.put(JsonTags.SHARELIB_UPDATE_HOST, otherUrl);
244                    errorJson.put(JsonTags.SHARELIB_UPDATE_STATUS, e.getMessage());
245                    JSONObject newJson = new JSONObject();
246                    newJson.put(JsonTags.SHARELIB_LIB_UPDATE, errorJson);
247                    jsonArray.add(newJson);
248                }
249            }
250            //For current server
251            JSONObject newJson = new JSONObject();
252            newJson.put(JsonTags.SHARELIB_LIB_UPDATE, updateLocalShareLib(request));
253            jsonArray.add(newJson);
254            sendJsonResponse(response, HttpServletResponse.SC_OK, jsonArray);
255        }
256        else {
257            JSONObject newJson = new JSONObject();
258            newJson.put(JsonTags.SHARELIB_LIB_UPDATE, updateLocalShareLib(request));
259            sendJsonResponse(response, HttpServletResponse.SC_OK, newJson);
260        }
261    }
262
263    @SuppressWarnings("unchecked")
264    private JSONObject updateLocalShareLib(HttpServletRequest request) {
265        ShareLibService shareLibService = Services.get().get(ShareLibService.class);
266        JSONObject json = new JSONObject();
267        json.put(JsonTags.SHARELIB_UPDATE_HOST, ConfigUtils.getOozieEffectiveUrl());
268        try {
269            json.putAll(shareLibService.updateShareLib());
270            json.put(JsonTags.SHARELIB_UPDATE_STATUS, "Successful");
271        }
272        catch (Exception e) {
273            json.put(JsonTags.SHARELIB_UPDATE_STATUS, e.getClass().getName() + ": " + e.getMessage());
274        }
275        return json;
276    }
277
278    /**
279     * Authorize request.
280     *
281     * @param request the HttpServletRequest
282     * @throws XServletException the x servlet exception
283     */
284    private void authorizeRequest(HttpServletRequest request) throws XServletException {
285        try {
286            AuthorizationService auth = Services.get().get(AuthorizationService.class);
287            auth.authorizeForAdmin(getUser(request), true);
288        }
289        catch (AuthorizationException ex) {
290            throw new XServletException(HttpServletResponse.SC_UNAUTHORIZED, ex);
291        }
292    }
293
294    @Override
295    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
296            IOException {
297    }
298
299    @SuppressWarnings("unchecked")
300    private <T> JSONArray instrElementsToJson(Map<String, Map<String, Instrumentation.Element<T>>> instrElements) {
301        JSONArray array = new JSONArray();
302        for (Map.Entry<String, Map<String, Instrumentation.Element<T>>> group : instrElements.entrySet()) {
303            JSONObject json = new JSONObject();
304            String groupName = group.getKey();
305            json.put(JsonTags.INSTR_GROUP, groupName);
306            JSONArray dataArray = new JSONArray();
307            for (Map.Entry<String, Instrumentation.Element<T>> elementEntry : group.getValue().entrySet()) {
308                String samplerName = elementEntry.getKey();
309                JSONObject dataJson = new JSONObject();
310                dataJson.put(JsonTags.INSTR_NAME, samplerName);
311                Object value = elementEntry.getValue().getValue();
312                if (value instanceof Instrumentation.Timer) {
313                    Instrumentation.Timer timer = (Instrumentation.Timer) value;
314                    dataJson.put(JsonTags.INSTR_TIMER_TICKS, timer.getTicks());
315                    dataJson.put(JsonTags.INSTR_TIMER_OWN_TIME_AVG, timer.getOwnAvg());
316                    dataJson.put(JsonTags.INSTR_TIMER_TOTAL_TIME_AVG, timer.getTotalAvg());
317                    dataJson.put(JsonTags.INSTR_TIMER_OWN_STD_DEV, timer.getOwnStdDev());
318                    dataJson.put(JsonTags.INSTR_TIMER_TOTAL_STD_DEV, timer.getTotalStdDev());
319                    dataJson.put(JsonTags.INSTR_TIMER_OWN_MIN_TIME, timer.getOwnMin());
320                    dataJson.put(JsonTags.INSTR_TIMER_OWN_MAX_TIME, timer.getOwnMax());
321                    dataJson.put(JsonTags.INSTR_TIMER_TOTAL_MIN_TIME, timer.getTotalMin());
322                    dataJson.put(JsonTags.INSTR_TIMER_TOTAL_MAX_TIME, timer.getTotalMax());
323                }
324                else {
325                    dataJson.put(JsonTags.INSTR_VARIABLE_VALUE, value);
326                }
327                dataArray.add(dataJson);
328            }
329            json.put(JsonTags.INSTR_DATA, dataArray);
330            array.add(json);
331        }
332        return array;
333    }
334
335    @SuppressWarnings("unchecked")
336    private JSONObject instrToJson(Instrumentation instr) {
337        JSONObject json = new JSONObject();
338        json.put(JsonTags.INSTR_VARIABLES, instrElementsToJson(instr.getVariables()));
339        json.put(JsonTags.INSTR_SAMPLERS, instrElementsToJson(instr.getSamplers()));
340        json.put(JsonTags.INSTR_COUNTERS, instrElementsToJson(instr.getCounters()));
341        json.put(JsonTags.INSTR_TIMERS, instrElementsToJson(instr.getTimers()));
342        return json;
343    }
344
345    protected abstract void populateOozieMode(JSONObject json);
346
347    protected abstract void setOozieMode(HttpServletRequest request, HttpServletResponse response, String resourceName)
348            throws XServletException;
349
350    protected abstract void getQueueDump(JSONObject json) throws XServletException;
351
352    private static final JSONArray GMTOffsetTimeZones = new JSONArray();
353    static {
354        prepareGMTOffsetTimeZones();
355    }
356
357    @SuppressWarnings({"unchecked", "rawtypes"})
358    private static void prepareGMTOffsetTimeZones() {
359        for (String tzId : new String[]{"GMT-12:00", "GMT-11:00", "GMT-10:00", "GMT-09:00", "GMT-08:00", "GMT-07:00", "GMT-06:00",
360                                        "GMT-05:00", "GMT-04:00", "GMT-03:00", "GMT-02:00", "GMT-01:00", "GMT+01:00", "GMT+02:00",
361                                        "GMT+03:00", "GMT+04:00", "GMT+05:00", "GMT+06:00", "GMT+07:00", "GMT+08:00", "GMT+09:00",
362                                        "GMT+10:00", "GMT+11:00", "GMT+12:00"}) {
363            TimeZone tz = TimeZone.getTimeZone(tzId);
364            JSONObject json = new JSONObject();
365            json.put(JsonTags.TIME_ZOME_DISPLAY_NAME, tz.getDisplayName(false, TimeZone.SHORT) + " (" + tzId + ")");
366            json.put(JsonTags.TIME_ZONE_ID, tzId);
367            GMTOffsetTimeZones.add(json);
368        }
369    }
370
371    @SuppressWarnings({"unchecked", "rawtypes"})
372    private JSONArray availableTimeZonesToJsonArray() {
373        JSONArray array = new JSONArray();
374        for (String tzId : TimeZone.getAvailableIDs()) {
375            // skip id's that are like "Etc/GMT+01:00" because their display names are like "GMT-01:00", which is confusing
376            if (!tzId.startsWith("Etc/GMT")) {
377                JSONObject json = new JSONObject();
378                TimeZone tZone = TimeZone.getTimeZone(tzId);
379                json.put(JsonTags.TIME_ZOME_DISPLAY_NAME, tZone.getDisplayName(false, TimeZone.SHORT) + " (" + tzId + ")");
380                json.put(JsonTags.TIME_ZONE_ID, tzId);
381                array.add(json);
382            }
383        }
384
385        // The combo box this populates cannot be edited, so the user can't type in GMT offsets (like in the CLI), so we'll add
386        // in some hourly offsets here (though the user will not be able to use other offsets without editing the cookie manually
387        // and they are not in order)
388        array.addAll(GMTOffsetTimeZones);
389
390        return array;
391    }
392
393    protected void sendInstrumentationResponse(HttpServletResponse response, Instrumentation instr)
394            throws IOException, XServletException {
395        sendJsonResponse(response, HttpServletResponse.SC_OK, instrToJson(instr));
396    }
397
398    protected abstract Map<String, String> getOozieURLs() throws XServletException;
399
400    protected abstract void sendMetricsResponse(HttpServletResponse response) throws IOException, XServletException;
401}