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