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     */
018    package org.apache.oozie.service;
019    
020    import org.apache.oozie.service.Service;
021    import org.apache.oozie.service.Services;
022    import org.apache.oozie.util.ParamChecker;
023    import org.apache.hadoop.conf.Configuration;
024    
025    import java.io.UnsupportedEncodingException;
026    import java.net.URLDecoder;
027    import java.text.MessageFormat;
028    
029    /**
030     * Service that generates and parses callback URLs.
031     */
032    public class CallbackService implements Service {
033    
034        public static final String CONF_PREFIX = Service.CONF_PREFIX + "CallbackService.";
035    
036        public static final String CONF_BASE_URL = CONF_PREFIX + "base.url";
037    
038        private Configuration oozieConf;
039    
040        /**
041         * Initialize the service.
042         *
043         * @param services services instance.
044         */
045        public void init(Services services) {
046            oozieConf = services.getConf();
047        }
048    
049        /**
050         * Destroy the service.
051         */
052        public void destroy() {
053        }
054    
055        /**
056         * Return the public interface of the Dag engine service.
057         *
058         * @return {@link CallbackService}.
059         */
060        public Class<? extends Service> getInterface() {
061            return CallbackService.class;
062        }
063    
064        private static final String ID_PARAM = "id=";
065        private static final String STATUS_PARAM = "status=";
066        private static final String CALL_BACK_QUERY_STRING = "{0}?" + ID_PARAM + "{1}" + "&" + STATUS_PARAM + "{2}&";
067    
068        /**
069         * Create a callback URL.
070         *
071         * @param actionId action ID for the callback URL.
072         * @param externalStatusVar variable for the caller to inject the external status.
073         * @return the callback URL.
074         */
075        public String createCallBackUrl(String actionId, String externalStatusVar) {
076            ParamChecker.notEmpty(actionId, "actionId");
077            ParamChecker.notEmpty(externalStatusVar, "externalStatusVar");
078            //TODO: figure out why double encoding is happening in case of hadoop callbacks.
079            String baseCallbackUrl = oozieConf.get(CONF_BASE_URL, "http://localhost:8080/oozie/v0/callback");
080            return MessageFormat.format(CALL_BACK_QUERY_STRING, baseCallbackUrl, actionId, externalStatusVar);
081        }
082    
083        private String getParam(String str, String name) {
084            String value = null;
085            int start = str.indexOf(name);
086            if (start > -1) {
087                int end = str.indexOf("&", start + 1);
088                start = start + name.length();
089                value = (end > -1) ? str.substring(start, end) : str.substring(start);
090            }
091            return value;
092        }
093    
094        /**
095         * Return if a callback URL is valid or not.
096         *
097         * @param callback callback URL (it can be just the callback portion of it).
098         * @return <code>true</code> if the callback URL is valid, <code>false</code> if it is not.
099         */
100        public boolean isValid(String callback) {
101            return callback != null && getParam(callback, ID_PARAM) != null && getParam(callback, STATUS_PARAM) != null;
102        }
103    
104        /**
105         * Return the action ID from a callback URL.
106         *
107         * @param callback callback URL (it can be just the callback portion of it).
108         * @return the action ID from a callback URL.
109         */
110        public String getActionId(String callback) {
111            try {
112                return URLDecoder.decode(getParam(ParamChecker.notEmpty(callback, "callback"), ID_PARAM), "UTF-8");
113            }
114            catch (UnsupportedEncodingException ex) {
115                throw new RuntimeException(ex);
116            }
117        }
118    
119        /**
120         * Return the action external status from a callback URL.
121         *
122         * @param callback callback URL (it can be just the callback portion of it).
123         * @return the action external status from a callback URL.
124         */
125        public String getExternalStatus(String callback) {
126            try {
127                return URLDecoder.decode(getParam(ParamChecker.notEmpty(callback, "callback"), STATUS_PARAM), "UTF-8");
128            }
129            catch (UnsupportedEncodingException ex) {
130                throw new RuntimeException(ex);
131            }
132        }
133    
134    }