001    /**
002     * Copyright (c) 2010 Yahoo! Inc. All rights reserved.
003     * Licensed under the Apache License, Version 2.0 (the "License");
004     * you may not use this file except in compliance with the License.
005     * 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
010     *  distributed under the License is distributed on an "AS IS" BASIS,
011     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012     *  See the License for the specific language governing permissions and
013     *  limitations under the License. See accompanying LICENSE file.
014     */
015    package org.apache.oozie.service;
016    
017    import org.apache.oozie.util.ParamChecker;
018    import org.apache.oozie.util.XLog;
019    import org.apache.oozie.ErrorCode;
020    
021    import java.text.SimpleDateFormat;
022    import java.util.Date;
023    import java.util.UUID;
024    import java.util.concurrent.atomic.AtomicLong;
025    
026    /**
027     * The UUID service generates unique IDs. <p/> The configuration property {@link #CONF_GENERATOR} specifies the ID
028     * generation type, 'random' or 'counter'. <p/> For 'random' uses the JDK UUID.randomUUID() method. <p/> For 'counter'
029     * uses a counter postfixed wit the system start up time.
030     */
031    public class UUIDService implements Service {
032    
033        public static final String CONF_PREFIX = Service.CONF_PREFIX + "UUIDService.";
034    
035        public static final String CONF_GENERATOR = CONF_PREFIX + "generator";
036    
037        private String startTime;
038        private AtomicLong counter;
039        private String systemId;
040    
041        /**
042         * Initialize the UUID service.
043         *
044         * @param services services instance.
045         * @throws ServiceException thrown if the UUID service could not be initialized.
046         */
047        @Override
048        public void init(Services services) throws ServiceException {
049            String genType = services.getConf().get(CONF_GENERATOR, "counter").trim();
050            if (genType.equals("counter")) {
051                counter = new AtomicLong();
052                startTime = new SimpleDateFormat("yyMMddHHmmssSSS").format(new Date());
053            }
054            else {
055                if (!genType.equals("random")) {
056                    throw new ServiceException(ErrorCode.E0120, genType);
057                }
058            }
059            systemId = services.getSystemId();
060        }
061    
062        /**
063         * Destroy the UUID service.
064         */
065        @Override
066        public void destroy() {
067            counter = null;
068            startTime = null;
069        }
070    
071        /**
072         * Return the public interface for UUID service.
073         *
074         * @return {@link UUIDService}.
075         */
076        @Override
077        public Class<? extends Service> getInterface() {
078            return UUIDService.class;
079        }
080    
081        private String longPadding(long number) {
082            StringBuilder sb = new StringBuilder();
083            sb.append(number);
084            if (sb.length() <= 7) {
085                sb.insert(0, "0000000".substring(sb.length()));
086            }
087            return sb.toString();
088        }
089    
090        /**
091         * Create a unique ID.
092         *
093         * @param type: Type of Id. Generally 'C' for Coordinator and 'W' for Workflow.
094         * @return unique ID.
095         */
096        public String generateId(ApplicationType type) {
097            StringBuilder sb = new StringBuilder();
098    
099            if (counter != null) {
100                sb.append(longPadding(counter.getAndIncrement())).append('-').append(startTime);
101            }
102            else {
103                sb.append(UUID.randomUUID().toString());
104                if (sb.length() > (37 - systemId.length())) {
105                    sb.setLength(37 - systemId.length());
106                }
107            }
108            sb.append('-').append(systemId);
109            sb.append('-').append(type.getType());
110            // limitation due to current DB schema for action ID length (100)
111            if (sb.length() > 40) {
112                throw new RuntimeException(XLog.format("ID exceeds limit of 40 characters, [{0}]", sb));
113            }
114            return sb.toString();
115        }
116    
117        /**
118         * Create a child ID. <p/> If the same child name is given the returned child ID is the same.
119         *
120         * @param id unique ID.
121         * @param childName child name.
122         * @return a child ID.
123         */
124        public String generateChildId(String id, String childName) {
125            id = ParamChecker.notEmpty(id, "id") + "@" + ParamChecker.notEmpty(childName, "childName");
126    
127            // limitation due to current DB schema for action ID length (100)
128            if (id.length() > 95) {
129                throw new RuntimeException(XLog.format("Child ID exceeds limit of 95 characters, [{0}]", id));
130            }
131            return id;
132        }
133    
134        /**
135         * Return the ID from a child ID.
136         *
137         * @param childId child ID.
138         * @return ID of the child ID.
139         */
140        public String getId(String childId) {
141            int index = ParamChecker.notEmpty(childId, "childId").indexOf("@");
142            if (index == -1) {
143                throw new IllegalArgumentException(XLog.format("invalid child id [{0}]", childId));
144            }
145            return childId.substring(0, index);
146        }
147    
148        /**
149         * Return the child name from a child ID.
150         *
151         * @param childId child ID.
152         * @return child name.
153         */
154        public String getChildName(String childId) {
155            int index = ParamChecker.notEmpty(childId, "childId").indexOf("@");
156            if (index == -1) {
157                throw new IllegalArgumentException(XLog.format("invalid child id [{0}]", childId));
158            }
159            return childId.substring(index + 1);
160        }
161    
162        public enum ApplicationType {
163            WORKFLOW('W'), COORDINATOR('C');
164            private char type;
165    
166            private ApplicationType(char type) {
167                this.type = type;
168            }
169    
170            public char getType() {
171                return type;
172            }
173        }
174    }