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