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    protected String startTime;
045    private AtomicLong counter;
046    private String systemId;
047
048    /**
049     * Initialize the UUID service.
050     *
051     * @param services services instance.
052     * @throws ServiceException thrown if the UUID service could not be initialized.
053     */
054    @Override
055    public void init(Services services) throws ServiceException {
056        String genType = services.getConf().get(CONF_GENERATOR, "counter").trim();
057        if (genType.equals("counter")) {
058            counter = new AtomicLong();
059            resetStartTime();
060        }
061        else {
062            if (!genType.equals("random")) {
063                throw new ServiceException(ErrorCode.E0120, genType);
064            }
065        }
066        systemId = services.getSystemId();
067    }
068
069    /**
070     * Destroy the UUID service.
071     */
072    @Override
073    public void destroy() {
074        counter = null;
075        startTime = null;
076    }
077
078    /**
079     * reset start time
080     * @return
081     */
082    protected void resetStartTime() {
083        startTime = new SimpleDateFormat("yyMMddHHmmssSSS").format(new Date());
084    }
085
086    /**
087     * Return the public interface for UUID service.
088     *
089     * @return {@link UUIDService}.
090     */
091    @Override
092    public Class<? extends Service> getInterface() {
093        return UUIDService.class;
094    }
095
096    protected String longPadding(long number) {
097        StringBuilder sb = new StringBuilder();
098        sb.append(number);
099        if (sb.length() <= 7) {
100            sb.insert(0, "0000000".substring(sb.length()));
101        }
102        return sb.toString();
103    }
104
105    /**
106     * Create a unique ID.
107     *
108     * @param type: Type of Id. Generally 'C' for Coordinator and 'W' for Workflow.
109     * @return unique ID.
110     */
111    public String generateId(ApplicationType type) {
112        StringBuilder sb = new StringBuilder();
113
114        sb.append(getSequence());
115        sb.append('-').append(systemId);
116        sb.append('-').append(type.getType());
117        // limitation due to current DB schema for action ID length (100)
118        if (sb.length() > 40) {
119            throw new RuntimeException(XLog.format("ID exceeds limit of 40 characters, [{0}]", sb));
120        }
121        return sb.toString();
122    }
123
124    private String getSequence() {
125        StringBuilder sb = new StringBuilder();
126        if (counter != null) {
127            sb.append(createSequence());
128        }
129        else {
130            sb.append(UUID.randomUUID().toString());
131            if (sb.length() > (37 - systemId.length())) {
132                sb.setLength(37 - systemId.length());
133            }
134        }
135        return sb.toString();
136    }
137
138    protected String createSequence() {
139        return appendTimeToSequence(getCounter(), startTime);
140    }
141
142    protected long getCounter() {
143        return counter.getAndIncrement();
144    }
145
146    protected String appendTimeToSequence(long id, String localStartTime) {
147        StringBuilder sb = new StringBuilder();
148        sb.append(longPadding(id)).append('-').append(localStartTime);
149        return sb.toString();
150    }
151
152    /**
153     * Create a child ID.
154     * <p/>
155     * If the same child name is given the returned child ID is the same.
156     *
157     * @param id unique ID.
158     * @param childName child name.
159     * @return a child ID.
160     */
161    public String generateChildId(String id, String childName) {
162        id = ParamChecker.notEmpty(id, "id") + "@" + ParamChecker.notEmpty(childName, "childName");
163
164        // limitation due to current DB schema for action ID length (100)
165        if (id.length() > 95) {
166            throw new RuntimeException(XLog.format("Child ID exceeds limit of 95 characters, [{0}]", id));
167        }
168        return id;
169    }
170
171    /**
172     * Return the ID from a child ID.
173     *
174     * @param childId child ID.
175     * @return ID of the child ID.
176     */
177    public String getId(String childId) {
178        int index = ParamChecker.notEmpty(childId, "childId").indexOf("@");
179        if (index == -1) {
180            throw new IllegalArgumentException(XLog.format("invalid child id [{0}]", childId));
181        }
182        return childId.substring(0, index);
183    }
184
185    /**
186     * Return the child name from a child ID.
187     *
188     * @param childId child ID.
189     * @return child name.
190     */
191    public String getChildName(String childId) {
192        int index = ParamChecker.notEmpty(childId, "childId").indexOf("@");
193        if (index == -1) {
194            throw new IllegalArgumentException(XLog.format("invalid child id [{0}]", childId));
195        }
196        return childId.substring(index + 1);
197    }
198
199    public enum ApplicationType {
200        WORKFLOW('W'), COORDINATOR('C'), BUNDLE('B');
201        private final char type;
202
203        private ApplicationType(char type) {
204            this.type = type;
205        }
206
207        public char getType() {
208            return type;
209        }
210    }
211}