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