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    private 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            startTime = new SimpleDateFormat("yyMMddHHmmssSSS").format(new Date());
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     * Return the public interface for UUID service.
080     *
081     * @return {@link UUIDService}.
082     */
083    @Override
084    public Class<? extends Service> getInterface() {
085        return UUIDService.class;
086    }
087
088    private String longPadding(long number) {
089        StringBuilder sb = new StringBuilder();
090        sb.append(number);
091        if (sb.length() <= 7) {
092            sb.insert(0, "0000000".substring(sb.length()));
093        }
094        return sb.toString();
095    }
096
097    /**
098     * Create a unique ID.
099     *
100     * @param type: Type of Id. Generally 'C' for Coordinator and 'W' for Workflow.
101     * @return unique ID.
102     */
103    public String generateId(ApplicationType type) {
104        StringBuilder sb = new StringBuilder();
105
106        if (counter != null) {
107            sb.append(longPadding(counter.getAndIncrement())).append('-').append(startTime);
108        }
109        else {
110            sb.append(UUID.randomUUID().toString());
111            if (sb.length() > (37 - systemId.length())) {
112                sb.setLength(37 - systemId.length());
113            }
114        }
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    /**
125     * Create a child ID.
126     * <p/>
127     * If the same child name is given the returned child ID is the same.
128     *
129     * @param id unique ID.
130     * @param childName child name.
131     * @return a child ID.
132     */
133    public String generateChildId(String id, String childName) {
134        id = ParamChecker.notEmpty(id, "id") + "@" + ParamChecker.notEmpty(childName, "childName");
135
136        // limitation due to current DB schema for action ID length (100)
137        if (id.length() > 95) {
138            throw new RuntimeException(XLog.format("Child ID exceeds limit of 95 characters, [{0}]", id));
139        }
140        return id;
141    }
142
143    /**
144     * Return the ID from a child ID.
145     *
146     * @param childId child ID.
147     * @return ID of the child ID.
148     */
149    public String getId(String childId) {
150        int index = ParamChecker.notEmpty(childId, "childId").indexOf("@");
151        if (index == -1) {
152            throw new IllegalArgumentException(XLog.format("invalid child id [{0}]", childId));
153        }
154        return childId.substring(0, index);
155    }
156
157    /**
158     * Return the child name from a child ID.
159     *
160     * @param childId child ID.
161     * @return child name.
162     */
163    public String getChildName(String childId) {
164        int index = ParamChecker.notEmpty(childId, "childId").indexOf("@");
165        if (index == -1) {
166            throw new IllegalArgumentException(XLog.format("invalid child id [{0}]", childId));
167        }
168        return childId.substring(index + 1);
169    }
170
171    public enum ApplicationType {
172        WORKFLOW('W'), COORDINATOR('C'), BUNDLE('B');
173        private final char type;
174
175        private ApplicationType(char type) {
176            this.type = type;
177        }
178
179        public char getType() {
180            return type;
181        }
182    }
183}