You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by db...@apache.org on 2016/04/26 09:03:44 UTC

[1/2] ambari git commit: AMBARI-15821 Ability to provide logical urls to view instances, Added URL wizard and other UI fixes(Ashwin Rajeev via dipayanb)

Repository: ambari
Updated Branches:
  refs/heads/trunk 614b12fc3 -> f62a92770


http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewURLResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewURLResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewURLResourceProvider.java
new file mode 100644
index 0000000..f3d6b11
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewURLResourceProvider.java
@@ -0,0 +1,358 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.controller.internal;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
+import com.google.inject.Inject;
+import com.google.inject.persist.Transactional;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.StaticallyInject;
+import org.apache.ambari.server.controller.predicate.EqualsPredicate;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.orm.dao.ViewURLDAO;
+import org.apache.ambari.server.orm.entities.ViewEntity;
+import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
+import org.apache.ambari.server.orm.entities.ViewURLEntity;
+import org.apache.ambari.server.security.authorization.RoleAuthorization;
+import org.apache.ambari.server.view.ViewRegistry;
+import org.apache.ambari.server.view.validation.ValidationException;
+
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Resource provider for view URLs.
+ */
+@SuppressWarnings("Duplicates")
+@StaticallyInject
+public class ViewURLResourceProvider extends AbstractAuthorizedResourceProvider {
+
+  /**
+   * view URL property id constants.
+   */
+  public static final String URL_NAME_PROPERTY_ID                       = "ViewUrlInfo/url_name";
+  public static final String URL_SUFFIX_PROPERTY_ID                     = "ViewUrlInfo/url_suffix";
+  public static final String VIEW_INSTANCE_VERSION_PROPERTY_ID          = "ViewUrlInfo/view_instance_version";
+  public static final String VIEW_INSTANCE_NAME_PROPERTY_ID             = "ViewUrlInfo/view_instance_name";
+  public static final String VIEW_INSTANCE_COMMON_NAME_PROPERTY_ID      = "ViewUrlInfo/view_instance_common_name";
+
+  /**
+   * The key property ids for a view URL resource.
+   */
+  private static Map<Resource.Type, String> keyPropertyIds = new HashMap<Resource.Type, String>();
+  static {
+    keyPropertyIds.put(Resource.Type.View, VIEW_INSTANCE_NAME_PROPERTY_ID);
+    keyPropertyIds.put(Resource.Type.ViewVersion, VIEW_INSTANCE_VERSION_PROPERTY_ID);
+    keyPropertyIds.put(Resource.Type.ViewInstance, VIEW_INSTANCE_COMMON_NAME_PROPERTY_ID);
+    keyPropertyIds.put(Resource.Type.ViewURL, URL_NAME_PROPERTY_ID);
+  }
+
+  /**
+   * The property ids for a view URL resource.
+   */
+  private static Set<String> propertyIds = new HashSet<String>();
+  static {
+    propertyIds.add(URL_NAME_PROPERTY_ID);
+    propertyIds.add(URL_SUFFIX_PROPERTY_ID);
+    propertyIds.add(VIEW_INSTANCE_VERSION_PROPERTY_ID);
+    propertyIds.add(VIEW_INSTANCE_NAME_PROPERTY_ID);
+    propertyIds.add(VIEW_INSTANCE_COMMON_NAME_PROPERTY_ID);
+  }
+
+  @Inject
+  private static ViewURLDAO viewURLDAO;
+
+
+  // ----- Constructors ------------------------------------------------------
+
+  /**
+   * Construct a view URL resource provider.
+   */
+  public ViewURLResourceProvider() {
+    super(propertyIds, keyPropertyIds);
+
+    EnumSet<RoleAuthorization> requiredAuthorizations = EnumSet.of(RoleAuthorization.AMBARI_MANAGE_VIEWS);
+    setRequiredCreateAuthorizations(requiredAuthorizations);
+    setRequiredDeleteAuthorizations(requiredAuthorizations);
+    setRequiredUpdateAuthorizations(requiredAuthorizations);
+  }
+
+
+  // ----- ResourceProvider --------------------------------------------------
+
+  @Override
+  protected RequestStatus createResourcesAuthorized(Request request)
+      throws SystemException, UnsupportedPropertyException,
+             ResourceAlreadyExistsException, NoSuchParentResourceException {
+    for (Map<String, Object> properties : request.getProperties()) {
+      createResources(getCreateCommand(properties));
+    }
+    notifyCreate(Resource.Type.ViewURL, request);
+
+    return getRequestStatus(null);
+  }
+
+  @Override
+  public Set<Resource> getResources(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    Set<Resource> resources    = Sets.newHashSet();
+    List<ViewURLEntity> urlEntities = viewURLDAO.findAll();
+    for (ViewURLEntity urlEntity : urlEntities) {
+      resources.add(toResource(urlEntity));
+    }
+
+    return resources;
+  }
+
+  @Override
+  protected RequestStatus updateResourcesAuthorized(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    Iterator<Map<String,Object>> iterator = request.getProperties().iterator();
+    if (iterator.hasNext()) {
+      for (Map<String, Object> propertyMap : getPropertyMaps(iterator.next(), predicate)) {
+        modifyResources(getUpdateCommand(propertyMap));
+      }
+    }
+    notifyUpdate(Resource.Type.ViewInstance, request, predicate);
+
+    return getRequestStatus(null);
+  }
+
+  @Override
+  protected RequestStatus deleteResourcesAuthorized(Request request, Predicate predicate)
+          throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+      modifyResources(getDeleteCommand(predicate));
+      notifyDelete(Resource.Type.ViewInstance, predicate);
+      return getRequestStatus(null);
+
+  }
+
+  @Override
+  public Map<Resource.Type, String> getKeyPropertyIds() {
+    return keyPropertyIds;
+  }
+
+
+  // ----- AbstractResourceProvider ------------------------------------------
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return new HashSet<String>(keyPropertyIds.values());
+  }
+
+
+  // ----- helper methods ----------------------------------------------------
+
+  /**
+   * Converts a View URL entity into a Resource.
+   * @param viewURLEntity
+   * @return A resource object representing the URL
+     */
+  protected Resource toResource(ViewURLEntity viewURLEntity) {
+    Resource   resource   = new ResourceImpl(Resource.Type.ViewURL);
+
+    resource.setProperty(URL_NAME_PROPERTY_ID,viewURLEntity.getUrlName());
+    resource.setProperty(URL_SUFFIX_PROPERTY_ID,viewURLEntity.getUrlSuffix());
+    ViewInstanceEntity viewInstanceEntity = viewURLEntity.getViewInstanceEntity();
+    if(viewInstanceEntity == null)
+      return resource;
+    ViewEntity viewEntity = viewInstanceEntity.getViewEntity();
+    String viewName = viewEntity.getCommonName();
+    String version  = viewEntity.getVersion();
+    String name     = viewInstanceEntity.getName();
+    resource.setProperty(VIEW_INSTANCE_NAME_PROPERTY_ID,name);
+    resource.setProperty(VIEW_INSTANCE_VERSION_PROPERTY_ID,version);
+    resource.setProperty(VIEW_INSTANCE_COMMON_NAME_PROPERTY_ID,viewName);
+    return resource;
+  }
+
+  /**
+   * Converts the incoming request into a View URL
+   * @param properties The property map
+   * @return The view URL
+   * @throws AmbariException
+     */
+  private ViewURLEntity toEntity(Map<String, Object> properties) throws AmbariException {
+    String name = (String) properties.get(URL_NAME_PROPERTY_ID);
+    if (name == null || name.isEmpty()) {
+      throw new IllegalArgumentException("The View URL is a required property.");
+    }
+
+    String suffix = (String) properties.get(URL_SUFFIX_PROPERTY_ID);
+    String commonName = (String) properties.get(VIEW_INSTANCE_COMMON_NAME_PROPERTY_ID);
+    String instanceName = (String) properties.get(VIEW_INSTANCE_NAME_PROPERTY_ID);
+    String instanceVersion = (String) properties.get(VIEW_INSTANCE_VERSION_PROPERTY_ID);
+    ViewRegistry viewRegistry = ViewRegistry.getInstance();
+    ViewInstanceEntity instanceEntity = viewRegistry.getInstanceDefinition(commonName, instanceVersion, instanceName);
+
+    ViewURLEntity urlEntity = new ViewURLEntity();
+    urlEntity.setUrlName(name);
+    urlEntity.setUrlSuffix(suffix);
+    urlEntity.setViewInstanceEntity(instanceEntity);
+
+    return urlEntity;
+
+  }
+
+  /**
+   * Get the command to create the View URL
+   * @param properties
+   * @return A command to create the View URL instance
+     */
+  private Command<Void> getCreateCommand(final Map<String, Object> properties) {
+    return new Command<Void>() {
+      @Transactional
+      @Override
+      public Void invoke() throws AmbariException {
+        ViewRegistry       viewRegistry   = ViewRegistry.getInstance();
+
+        ViewURLEntity urlEntity = toEntity(properties);
+
+        ViewInstanceEntity viewInstanceEntity = urlEntity.getViewInstanceEntity();
+        ViewEntity viewEntity = viewInstanceEntity.getViewEntity();
+        String     viewName   = viewEntity.getCommonName();
+        String     version    = viewEntity.getVersion();
+        ViewEntity view       = viewRegistry.getDefinition(viewName, version);
+
+        if ( view == null ) {
+          throw new IllegalStateException("The view " + viewName + " is not registered.");
+        }
+
+        // the view must be in the DEPLOYED state to create an instance
+        if (!view.isDeployed()) {
+          throw new IllegalStateException("The view " + viewName + " is not loaded.");
+        }
+
+        ViewURLEntity viewUrl = viewInstanceEntity.getViewUrl();
+        Optional<ViewURLEntity> savedUrl = viewURLDAO.findByName(urlEntity.getUrlName());
+
+        if(savedUrl.isPresent()){
+          throw new AmbariException("This view URL name exists, URL names should be unique");
+        }
+
+        if(viewUrl != null) {
+          throw new AmbariException("The view instance selected already has a linked URL");
+        }
+
+        viewURLDAO.save(urlEntity);
+        // Update the view with the URL
+        viewInstanceEntity.setViewUrl(urlEntity);
+        try {
+          viewRegistry.updateViewInstance(viewInstanceEntity);
+        } catch (ValidationException e) {
+          throw new IllegalArgumentException(e.getMessage(), e);
+        } catch (org.apache.ambari.view.SystemException e) {
+          throw new AmbariException("Caught exception trying to update view URL.", e);
+        }
+        viewRegistry.updateView(viewInstanceEntity);
+        return null;
+      }
+    };
+  }
+
+  /**
+   * Get the command to update the View URL
+   * @param properties
+   * @return The update command
+     */
+  private Command<Void> getUpdateCommand(final Map<String, Object> properties) {
+    return new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        ViewRegistry registry = ViewRegistry.getInstance();
+        String name = (String) properties.get(URL_NAME_PROPERTY_ID);
+        String suffix = (String) properties.get(URL_SUFFIX_PROPERTY_ID);
+        Optional<ViewURLEntity> entity = viewURLDAO.findByName(name);
+        if(!entity.isPresent()){
+          throw new AmbariException("URL with name "+ name +"was not found");
+        }
+        entity.get().setUrlSuffix(suffix);
+        viewURLDAO.update(entity.get());
+        // update the instance to sync with the DB values
+        ViewInstanceEntity viewInstanceEntity = entity.get().getViewInstanceEntity();
+        try {
+          registry.updateViewInstance(viewInstanceEntity);
+        } catch (ValidationException e) {
+          throw new IllegalArgumentException(e.getMessage(), e);
+        } catch (org.apache.ambari.view.SystemException e) {
+          throw new AmbariException("Caught exception trying to update view URL.", e);
+        }
+         registry.updateView(viewInstanceEntity);
+        return null;
+      }
+    };
+  }
+
+  /**
+   * Get the command to delete the View URL
+   * @param predicate
+   * @return The delete command
+     */
+  private Command<Void> getDeleteCommand(final Predicate predicate) {
+    return new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        ViewRegistry viewRegistry = ViewRegistry.getInstance();
+        Comparable deletedUrl = ((EqualsPredicate) predicate).getValue();
+        String toDelete = deletedUrl.toString();
+        Optional<ViewURLEntity> urlEntity = viewURLDAO.findByName(toDelete);
+        if(!urlEntity.isPresent()){
+          throw new AmbariException("The URL "+ toDelete +"does not exist");
+        }
+        ViewInstanceEntity viewInstanceEntity = urlEntity.get().getViewInstanceEntity();
+        if(viewInstanceEntity != null) {
+          viewInstanceEntity.clearUrl();
+
+          try {
+            viewRegistry.updateViewInstance(viewInstanceEntity);
+          } catch (ValidationException e) {
+            throw new IllegalArgumentException(e.getMessage(), e);
+          } catch (org.apache.ambari.view.SystemException e) {
+            throw new AmbariException("Caught exception trying to update view URL.", e);
+          }
+
+          viewRegistry.updateView(viewInstanceEntity);
+        }
+        // Delete the url
+        urlEntity.get().clearEntity();
+        viewURLDAO.delete(urlEntity.get());
+        return null;
+      }
+    };
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
index 5a8476d..386e657 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
@@ -109,6 +109,7 @@ public interface Resource {
     RootServiceComponent,
     RootServiceHostComponent,
     View,
+    ViewURL,
     ViewVersion,
     ViewInstance,
     Blueprint,
@@ -225,6 +226,7 @@ public interface Resource {
     public static final Type RootServiceComponent = InternalType.RootServiceComponent.getType();
     public static final Type RootServiceHostComponent = InternalType.RootServiceHostComponent.getType();
     public static final Type View = InternalType.View.getType();
+    public static final Type ViewURL = InternalType.ViewURL.getType();
     public static final Type ViewVersion = InternalType.ViewVersion.getType();
     public static final Type ViewInstance = InternalType.ViewInstance.getType();
     public static final Type Blueprint = InternalType.Blueprint.getType();

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewURLDAO.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewURLDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewURLDAO.java
new file mode 100644
index 0000000..6a03489
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewURLDAO.java
@@ -0,0 +1,111 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.orm.dao;
+
+import com.google.common.base.Optional;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
+import org.apache.ambari.server.orm.RequiresSession;
+import org.apache.ambari.server.orm.entities.ViewInstanceDataEntity;
+import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
+import org.apache.ambari.server.orm.entities.ViewURLEntity;
+
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+import java.util.List;
+
+/**
+ * View Instance Data Access Object.
+ */
+@Singleton
+public class ViewURLDAO {
+  /**
+   * JPA entity manager
+   */
+  @Inject
+  private Provider<EntityManager> entityManagerProvider;
+  @Inject
+  private DaoUtils daoUtils;
+
+
+  /**
+   * Find all view instances.
+   *
+   * @return all views or an empty List
+   */
+  @RequiresSession
+  public List<ViewURLEntity> findAll() {
+    TypedQuery<ViewURLEntity> query = entityManagerProvider.get().
+        createNamedQuery("allViewUrls", ViewURLEntity.class);
+
+    return query.getResultList();
+  }
+
+  /**
+   * Find URL by name
+   * @param urlName
+   * @return
+     */
+  @RequiresSession
+  public Optional<ViewURLEntity> findByName(String urlName) {
+    TypedQuery<ViewURLEntity> query = entityManagerProvider.get().
+            createNamedQuery("viewUrlByName", ViewURLEntity.class);
+    query.setParameter("urlName", urlName);
+    try {
+      return Optional.of(query.getSingleResult());
+    } catch (Exception e){
+      return Optional.absent();
+    }
+  }
+
+  /**
+   * Save a URL entity
+   * @param urlEntity
+     */
+  @Transactional
+  public void save(ViewURLEntity urlEntity) {
+    entityManagerProvider.get().persist(urlEntity);
+    // Reverse mappings are not automatically flushed for some reason
+    entityManagerProvider.get().flush();
+  }
+
+  /**
+   * Update and merge a URL entity
+   * @param entity
+     */
+  @Transactional
+  public void update(ViewURLEntity entity) {
+    entityManagerProvider.get().merge(entity);
+    entityManagerProvider.get().flush();
+
+  }
+
+  /**
+   * Remove a URL entity
+   * @param urlEntity
+     */
+  @Transactional
+  public void delete(ViewURLEntity urlEntity) {
+    entityManagerProvider.get().remove(urlEntity);
+    entityManagerProvider.get().flush();
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java
index 772b384..2d6e5ba 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java
@@ -136,9 +136,11 @@ public class ViewInstanceEntity implements ViewInstanceDefinition {
   private String icon;
 
 
-  @Column(name = "short_url")
-  @Basic
-  private String shortUrl;
+  @OneToOne(cascade = CascadeType.ALL)
+  @JoinColumns({
+          @JoinColumn(name = "short_url", referencedColumnName = "url_id", nullable = true)
+  })
+  private ViewURLEntity viewUrl;
 
   /**
    * The big icon path.
@@ -249,7 +251,6 @@ public class ViewInstanceEntity implements ViewInstanceDefinition {
     this.clusterHandle = null;
     this.visible = instanceConfig.isVisible() ? 'Y' : 'N';
     this.alterNames = 1;
-    this.shortUrl = instanceConfig.getShortUrl();
 
     String label = instanceConfig.getLabel();
     this.label = (label == null || label.length() == 0) ? view.getLabel() : label;
@@ -288,7 +289,6 @@ public class ViewInstanceEntity implements ViewInstanceDefinition {
     this.visible = 'Y';
     this.alterNames = 1;
     this.label = label;
-    this.shortUrl = null;
   }
 
 
@@ -355,10 +355,6 @@ public class ViewInstanceEntity implements ViewInstanceDefinition {
   }
 
 
-  @Override
-  public String getShortUrl() {
-    return shortUrl;
-  }
 
 
 
@@ -399,14 +395,6 @@ public class ViewInstanceEntity implements ViewInstanceDefinition {
 
 
   /**
-   *  Set the short URL
-   * @param shortUrl
-   */
-  public void setShortUrl(String shortUrl) {
-    this.shortUrl = shortUrl;
-  }
-
-  /**
    * Get the name of this instance.
    *
    * @return the instance name
@@ -956,6 +944,29 @@ public class ViewInstanceEntity implements ViewInstanceDefinition {
     return result;
   }
 
+  /**
+   * Get the view URL associated with the instance
+   * @return
+     */
+  public ViewURLEntity getViewUrl() {
+    return viewUrl;
+  }
+
+  /**
+   * Set the view URL associated with the instance
+   * @param viewUrl
+     */
+  public void setViewUrl(ViewURLEntity viewUrl) {
+    this.viewUrl = viewUrl;
+  }
+
+  /**
+   * Remove the URL associated with this entity
+   */
+  public void clearUrl() {
+    this.viewUrl = null;
+  }
+
   //----- ViewInstanceVersionDTO inner class --------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewURLEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewURLEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewURLEntity.java
new file mode 100644
index 0000000..c0ed24f
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewURLEntity.java
@@ -0,0 +1,144 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.orm.entities;
+
+import javax.persistence.Basic;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinColumns;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.TableGenerator;
+
+/**
+ * Represents an entity of a View.
+ */
+@Table(name = "viewurl")
+@TableGenerator(name = "viewurl_id_generator",
+    table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value"
+    , pkColumnValue = "viewurl_id_seq"
+    , initialValue = 1
+)
+
+@NamedQueries({
+        @NamedQuery(name = "allViewUrls",
+                query = "SELECT viewUrl FROM ViewURLEntity viewUrl"),
+        @NamedQuery(name = "viewUrlByName", query =
+                "SELECT viewUrlEntity " +
+                        "FROM ViewURLEntity viewUrlEntity " +
+                        "WHERE viewUrlEntity.urlName=:urlName")})
+
+
+@Entity
+public class ViewURLEntity {
+
+  @Column(name = "url_id")
+  @Id
+  @GeneratedValue(strategy = GenerationType.TABLE, generator = "viewentity_id_generator")
+  private Long id;
+
+  /**
+   * The view name.
+   */
+  @Column(name = "url_name", nullable = false, insertable = true, updatable = false)
+  private String urlName;
+
+  @Column(name = "url_suffix", nullable = false, insertable = true, updatable = true)
+  private String urlSuffix;
+
+  @OneToOne(fetch= FetchType.LAZY, mappedBy="viewUrl")
+  private ViewInstanceEntity viewInstanceEntity;
+
+
+  /**
+   * Get the URL suffix
+   * @return URL suffix
+     */
+  public String getUrlSuffix() {
+    return urlSuffix;
+  }
+
+  /**
+   * Set the URL suffix
+   * @param URL suffix
+     */
+  public void setUrlSuffix(String urlSuffix) {
+    this.urlSuffix = urlSuffix;
+  }
+
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  /**
+   * Get the URL name.
+   *
+   * @return the URL name
+   */
+  public String getUrlName() {
+    return urlName;
+  }
+
+  /**
+   * Set the URL name
+   *
+   * @param urlName  the URL name
+   */
+  public void setUrlName(String urlName) {
+    this.urlName = urlName;
+  }
+
+  /**
+   *  Get the linked instance entity
+   * @return viewInstanceEntity
+     */
+  public ViewInstanceEntity getViewInstanceEntity() {
+    return viewInstanceEntity;
+  }
+
+  /**
+   * Set the URL instance entity
+   * @param viewInstanceEntity
+     */
+  public void setViewInstanceEntity(ViewInstanceEntity viewInstanceEntity) {
+    this.viewInstanceEntity = viewInstanceEntity;
+  }
+
+  /**
+   * Remove the Instance entity associated with this View URL
+   */
+  public void clearEntity() {
+    this.viewInstanceEntity = null;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java
index e76bc5c..14cb42c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java
@@ -108,6 +108,8 @@ public class UpgradeCatalog240 extends AbstractUpgradeCatalog {
   private static final String HIVE_ENV_CONFIG = "hive-env";
   private static final String AMS_SITE = "ams-site";
   public static final String TIMELINE_METRICS_SINK_COLLECTION_PERIOD = "timeline.metrics.sink.collection.period";
+  public static final String VIEWURL_TABLE = "viewurl";
+  public static final String URL_ID_COLUMN = "url_id";
 
 
   @Inject
@@ -173,13 +175,28 @@ public class UpgradeCatalog240 extends AbstractUpgradeCatalog {
     updateAlertCurrentTable();
     createBlueprintSettingTable();
     updateHostRoleCommandTableDDL();
+    createViewUrlTableDDL();
     updateViewInstanceEntityTable();
+  }
+
+  private void createViewUrlTableDDL() throws SQLException {
+    List<DBColumnInfo> columns = new ArrayList<>();
+
+    //  Add setting table
+    LOG.info("Creating " + VIEWURL_TABLE + " table");
 
+    columns.add(new DBColumnInfo(URL_ID_COLUMN, Long.class, null, null, false));
+    columns.add(new DBColumnInfo("url_name", String.class, 255, null, false));
+    columns.add(new DBColumnInfo("url_suffix", String.class, 255, null, false));
+    dbAccessor.createTable(VIEWURL_TABLE, columns, URL_ID_COLUMN);
+    addSequence("viewurl_id_seq", 1L, false);
   }
 
   private void updateViewInstanceEntityTable() throws SQLException {
     dbAccessor.addColumn(VIEWINSTANCE_TABLE,
-      new DBColumnInfo(SHORT_URL_COLUMN, String.class, 255, null, true));
+      new DBColumnInfo(SHORT_URL_COLUMN, Long.class, null, null, true));
+    dbAccessor.addFKConstraint(VIEWINSTANCE_TABLE, "FK_instance_url_id",
+            SHORT_URL_COLUMN, VIEWURL_TABLE, URL_ID_COLUMN, false);
   }
 
   private void updateClusterTableDDL() throws SQLException {

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
index c29fe0e..d23fcad 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
@@ -496,25 +496,6 @@ public class ViewRegistry {
         (getInstanceDefinition(viewEntity.getCommonName(), viewEntity.getVersion(), instanceEntity.getName()) != null);
   }
 
-
-  public boolean duplicatedShortUrl(ViewInstanceEntity instanceEntity) {
-    ViewEntity viewEntity = getDefinition(instanceEntity.getViewName());
-    Map<String, ViewInstanceEntity> viewInstanceDefinitionMap =
-            viewInstanceDefinitions.get(getDefinition(viewEntity.getCommonName(), viewEntity.getVersion()));
-
-    if(viewInstanceDefinitionMap != null){
-      for (ViewInstanceEntity viewInstanceEntity : viewInstanceDefinitionMap.values()) {
-        String shortUrl = viewInstanceEntity.getShortUrl();
-        // check if there is a view for the same version with the same shortUrl
-        if (!Strings.isNullOrEmpty(shortUrl) && shortUrl.equals(instanceEntity.getShortUrl()))
-          return true;
-      }
-    }
-
-    return false;
-  }
-
-
   /**
    * Install the given view instance with its associated view.
    *
@@ -1408,7 +1389,7 @@ public class ViewRegistry {
   private void syncViewInstance(ViewInstanceEntity instance1, ViewInstanceEntity instance2) {
     instance1.setLabel(instance2.getLabel());
     instance1.setDescription(instance2.getDescription());
-    instance1.setShortUrl(instance2.getShortUrl());
+    instance1.setViewUrl(instance2.getViewUrl());
     instance1.setVisible(instance2.isVisible());
     instance1.setResource(instance2.getResource());
     instance1.setViewInstanceId(instance2.getViewInstanceId());

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/view/configuration/InstanceConfig.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/configuration/InstanceConfig.java b/ambari-server/src/main/java/org/apache/ambari/server/view/configuration/InstanceConfig.java
index 65b36df..764d697 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/view/configuration/InstanceConfig.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/configuration/InstanceConfig.java
@@ -138,19 +138,4 @@ public class InstanceConfig {
     return properties == null ? Collections.<PropertyConfig>emptyList() : properties;
   }
 
-  /**
-   * Get the short URL
-   * @return short URL
-   */
-  public String getShortUrl() {
-    return shortUrl;
-  }
-
-  /**
-   * Set the short URL
-   * @param shortUrl
-   */
-  public void setShortUrl(String shortUrl) {
-    this.shortUrl = shortUrl;
-  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
index 93576f7..319afa5 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
@@ -545,6 +545,15 @@ CREATE TABLE viewmain (
   CONSTRAINT PK_viewmain PRIMARY KEY (view_name),
   CONSTRAINT FK_view_resource_type_id FOREIGN KEY (resource_type_id) REFERENCES adminresourcetype(resource_type_id));
 
+
+CREATE table viewurl(
+  url_id BIGINT ,
+  url_name VARCHAR(255) NOT NULL ,
+  url_suffix VARCHAR(255) NOT NULL,
+  PRIMARY KEY(url_id)
+);
+
+
 CREATE TABLE viewinstance (
   view_instance_id BIGINT,
   resource_id BIGINT NOT NULL,
@@ -558,8 +567,9 @@ CREATE TABLE viewinstance (
   xml_driven CHAR(1),
   alter_names SMALLINT NOT NULL DEFAULT 1,
   cluster_handle VARCHAR(255),
-  short_url VARCHAR (255),
+  short_url BIGINT,
   CONSTRAINT PK_viewinstance PRIMARY KEY (view_instance_id),
+  CONSTRAINT FK_instance_url_id FOREIGN KEY (short_url) REFERENCES viewurl(url_id),
   CONSTRAINT FK_viewinst_view_name FOREIGN KEY (view_name) REFERENCES viewmain(view_name),
   CONSTRAINT FK_viewinstance_resource_id FOREIGN KEY (resource_id) REFERENCES adminresource(resource_id),
   CONSTRAINT UQ_viewinstance_name UNIQUE (view_name, name),
@@ -575,6 +585,7 @@ CREATE TABLE viewinstancedata (
   CONSTRAINT PK_viewinstancedata PRIMARY KEY (view_instance_id, name, user_name),
   CONSTRAINT FK_viewinstdata_view_name FOREIGN KEY (view_instance_id, view_name, view_instance_name) REFERENCES viewinstance(view_instance_id, view_name, name));
 
+
 CREATE TABLE viewinstanceproperty (
   view_name VARCHAR(255) NOT NULL,
   view_instance_name VARCHAR(255) NOT NULL,

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
index b0264f2..bdbf461 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
@@ -553,6 +553,15 @@ CREATE TABLE viewmain (
   CONSTRAINT PK_viewmain PRIMARY KEY (view_name),
   CONSTRAINT FK_view_resource_type_id FOREIGN KEY (resource_type_id) REFERENCES adminresourcetype(resource_type_id));
 
+
+CREATE table viewurl(
+  url_id BIGINT ,
+  url_name VARCHAR(255) NOT NULL ,
+  url_suffix VARCHAR(255) NOT NULL,
+  PRIMARY KEY(url_id)
+);
+
+
 CREATE TABLE viewinstance (
   view_instance_id BIGINT,
   resource_id BIGINT NOT NULL,
@@ -566,8 +575,9 @@ CREATE TABLE viewinstance (
   xml_driven CHAR(1),
   alter_names TINYINT(1) NOT NULL DEFAULT 1,
   cluster_handle VARCHAR(255),
-  short_url VARCHAR (255),
+  short_url BIGINT,
   CONSTRAINT PK_viewinstance PRIMARY KEY (view_instance_id),
+  CONSTRAINT FK_instance_url_id FOREIGN KEY (short_url) REFERENCES viewurl(url_id),
   CONSTRAINT FK_viewinst_view_name FOREIGN KEY (view_name) REFERENCES viewmain(view_name),
   CONSTRAINT FK_viewinstance_resource_id FOREIGN KEY (resource_id) REFERENCES adminresource(resource_id),
   CONSTRAINT UQ_viewinstance_name UNIQUE (view_name, name),

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
index 56a6616..e1da719 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
@@ -544,6 +544,15 @@ CREATE TABLE viewmain (view_name VARCHAR(255) NOT NULL,
   CONSTRAINT PK_viewmain PRIMARY KEY (view_name),
   CONSTRAINT FK_view_resource_type_id FOREIGN KEY (resource_type_id) REFERENCES adminresourcetype(resource_type_id));
 
+
+CREATE table viewurl(
+  url_id NUMBER ,
+  url_name VARCHAR(255) NOT NULL ,
+  url_suffix VARCHAR(255) NOT NULL,
+  PRIMARY KEY(url_id)
+);
+
+
 CREATE TABLE viewinstance (
   view_instance_id NUMBER(19),
   resource_id NUMBER(19) NOT NULL,
@@ -557,8 +566,9 @@ CREATE TABLE viewinstance (
   xml_driven CHAR(1),
   alter_names NUMBER(1) DEFAULT 1 NOT NULL,
   cluster_handle VARCHAR(255),
-  short_url VARCHAR (255),
+  short_url NUMBER,
   CONSTRAINT PK_viewinstance PRIMARY KEY (view_instance_id),
+  CONSTRAINT FK_instance_url_id FOREIGN KEY (short_url) REFERENCES viewurl(url_id),
   CONSTRAINT FK_viewinst_view_name FOREIGN KEY (view_name) REFERENCES viewmain(view_name),
   CONSTRAINT FK_viewinstance_resource_id FOREIGN KEY (resource_id) REFERENCES adminresource(resource_id),
   CONSTRAINT UQ_viewinstance_name UNIQUE (view_name, name),

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
index f18cdec..ab1eec4 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
@@ -544,6 +544,16 @@ CREATE TABLE viewmain (
   CONSTRAINT PK_viewmain PRIMARY KEY (view_name),
   CONSTRAINT FK_view_resource_type_id FOREIGN KEY (resource_type_id) REFERENCES adminresourcetype(resource_type_id));
 
+
+
+CREATE table viewurl(
+  url_id BIGINT ,
+  url_name VARCHAR(255) NOT NULL ,
+  url_suffix VARCHAR(255) NOT NULL,
+  PRIMARY KEY(url_id)
+);
+
+
 CREATE TABLE viewinstance (
   view_instance_id BIGINT,
   resource_id BIGINT NOT NULL,
@@ -557,8 +567,9 @@ CREATE TABLE viewinstance (
   xml_driven CHAR(1),
   alter_names SMALLINT NOT NULL DEFAULT 1,
   cluster_handle VARCHAR(255),
-  short_url VARCHAR (255),
+  short_url BIGINT,
   CONSTRAINT PK_viewinstance PRIMARY KEY (view_instance_id),
+  CONSTRAINT FK_instance_url_id FOREIGN KEY (short_url) REFERENCES viewurl(url_id),
   CONSTRAINT FK_viewinst_view_name FOREIGN KEY (view_name) REFERENCES viewmain(view_name),
   CONSTRAINT FK_viewinstance_resource_id FOREIGN KEY (resource_id) REFERENCES adminresource(resource_id),
   CONSTRAINT UQ_viewinstance_name UNIQUE (view_name, name),
@@ -574,6 +585,7 @@ CREATE TABLE viewinstancedata (
   CONSTRAINT PK_viewinstancedata PRIMARY KEY (view_instance_id, name, user_name),
   CONSTRAINT FK_viewinstdata_view_name FOREIGN KEY (view_instance_id, view_name, view_instance_name) REFERENCES viewinstance(view_instance_id, view_name, name));
 
+
 CREATE TABLE viewinstanceproperty (
   view_name VARCHAR(255) NOT NULL,
   view_instance_name VARCHAR(255) NOT NULL,

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
index 4584d5e..9d5f5df 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
@@ -645,6 +645,16 @@ CREATE TABLE ambari.viewmain (
   CONSTRAINT PK_viewmain PRIMARY KEY (view_name),
   CONSTRAINT FK_view_resource_type_id FOREIGN KEY (resource_type_id) REFERENCES ambari.adminresourcetype(resource_type_id));
 
+
+
+CREATE table ambari.viewurl(
+  url_id BIGINT ,
+  url_name VARCHAR(255) NOT NULL ,
+  url_suffix VARCHAR(255) NOT NULL,
+  PRIMARY KEY(url_id)
+);
+
+
 CREATE TABLE ambari.viewinstance (
   view_instance_id BIGINT,
   resource_id BIGINT NOT NULL,
@@ -658,8 +668,9 @@ CREATE TABLE ambari.viewinstance (
   xml_driven CHAR(1),
   alter_names SMALLINT NOT NULL DEFAULT 1,
   cluster_handle VARCHAR(255),
-  short_url VARCHAR (255),
+  short_url BIGINT,
   CONSTRAINT PK_viewinstance PRIMARY KEY (view_instance_id),
+  CONSTRAINT FK_instance_url_id FOREIGN KEY (short_url) REFERENCES ambari.viewurl(url_id),
   CONSTRAINT FK_viewinst_view_name FOREIGN KEY (view_name) REFERENCES ambari.viewmain(view_name),
   CONSTRAINT FK_viewinstance_resource_id FOREIGN KEY (resource_id) REFERENCES ambari.adminresource(resource_id),
   CONSTRAINT UQ_viewinstance_name UNIQUE (view_name, name),

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
index d2737d7..fdba489 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
@@ -542,6 +542,15 @@ CREATE TABLE viewmain (
   CONSTRAINT PK_viewmain PRIMARY KEY (view_name),
   CONSTRAINT FK_view_resource_type_id FOREIGN KEY (resource_type_id) REFERENCES adminresourcetype(resource_type_id));
 
+
+CREATE table viewurl(
+  url_id NUMERIC ,
+  url_name VARCHAR(255) NOT NULL ,
+  url_suffix VARCHAR(255) NOT NULL,
+  PRIMARY KEY(url_id)
+);
+
+
 CREATE TABLE viewinstance (
   view_instance_id NUMERIC(19),
   resource_id NUMERIC(19) NOT NULL,
@@ -555,8 +564,9 @@ CREATE TABLE viewinstance (
   xml_driven CHAR(1),
   alter_names BIT NOT NULL DEFAULT 1,
   cluster_handle VARCHAR(255),
-  short_url VARCHAR (255),
+  short_url NUMERIC,
   CONSTRAINT PK_viewinstance PRIMARY KEY (view_instance_id),
+  CONSTRAINT FK_instance_url_id FOREIGN KEY (short_url) REFERENCES viewurl(url_id),
   CONSTRAINT FK_viewinst_view_name FOREIGN KEY (view_name) REFERENCES viewmain(view_name),
   CONSTRAINT FK_viewinstance_resource_id FOREIGN KEY (resource_id) REFERENCES adminresource(resource_id),
   CONSTRAINT UQ_viewinstance_name UNIQUE (view_name, name),

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
index c85ae46..a1da8e5 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
@@ -552,6 +552,15 @@ CREATE TABLE viewmain (
   CONSTRAINT PK_viewmain PRIMARY KEY CLUSTERED (view_name),
   CONSTRAINT FK_view_resource_type_id FOREIGN KEY (resource_type_id) REFERENCES adminresourcetype(resource_type_id));
 
+
+CREATE table viewurl(
+  url_id BIGINT ,
+  url_name VARCHAR(255) NOT NULL ,
+  url_suffix VARCHAR(255) NOT NULL,
+  PRIMARY KEY CLUSTERED (url_id)
+);
+
+
 CREATE TABLE viewinstance (
   view_instance_id BIGINT,
   resource_id BIGINT NOT NULL,
@@ -565,8 +574,9 @@ CREATE TABLE viewinstance (
   xml_driven CHAR(1),
   alter_names BIT NOT NULL DEFAULT 1,
   cluster_handle VARCHAR(255),
-  short_url VARCHAR (255),
+  short_url BIGINT,
   CONSTRAINT PK_viewinstance PRIMARY KEY CLUSTERED (view_instance_id),
+  CONSTRAINT FK_instance_url_id FOREIGN KEY (short_url) REFERENCES viewurl(url_id),
   CONSTRAINT FK_viewinst_view_name FOREIGN KEY (view_name) REFERENCES viewmain(view_name),
   CONSTRAINT FK_viewinstance_resource_id FOREIGN KEY (resource_id) REFERENCES adminresource(resource_id),
   CONSTRAINT UQ_viewinstance_name UNIQUE (view_name, name),
@@ -582,6 +592,7 @@ CREATE TABLE viewinstancedata (
   CONSTRAINT PK_viewinstancedata PRIMARY KEY CLUSTERED (view_instance_id, NAME, user_name),
   CONSTRAINT FK_viewinstdata_view_name FOREIGN KEY (view_instance_id, view_name, view_instance_name) REFERENCES viewinstance(view_instance_id, view_name, name));
 
+
 CREATE TABLE viewinstanceproperty (
   view_name VARCHAR(255) NOT NULL,
   view_instance_name VARCHAR(255) NOT NULL,

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/resources/META-INF/persistence.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/META-INF/persistence.xml b/ambari-server/src/main/resources/META-INF/persistence.xml
index 3eff96f..ce563cb 100644
--- a/ambari-server/src/main/resources/META-INF/persistence.xml
+++ b/ambari-server/src/main/resources/META-INF/persistence.xml
@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
-  license agreements. See the NOTICE file distributed with this work for additional 
-  information regarding copyright ownership. The ASF licenses this file to 
-  You under the Apache License, Version 2.0 (the "License"); you may not use 
-  this file except in compliance with the License. You may obtain a copy of 
-  the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required 
-  by applicable law or agreed to in writing, software distributed under the 
-  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
-  OF ANY KIND, either express or implied. See the License for the specific 
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+  license agreements. See the NOTICE file distributed with this work for additional
+  information regarding copyright ownership. The ASF licenses this file to
+  You under the Apache License, Version 2.0 (the "License"); you may not use
+  this file except in compliance with the License. You may obtain a copy of
+  the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+  by applicable law or agreed to in writing, software distributed under the
+  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+  OF ANY KIND, either express or implied. See the License for the specific
   language governing permissions and limitations under the License. -->
 <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
 
@@ -78,6 +78,7 @@
     <class>org.apache.ambari.server.orm.entities.ViewEntityEntity</class>
     <class>org.apache.ambari.server.orm.entities.ViewInstanceDataEntity</class>
     <class>org.apache.ambari.server.orm.entities.ViewInstanceEntity</class>
+    <class>org.apache.ambari.server.orm.entities.ViewURLEntity</class>
     <class>org.apache.ambari.server.orm.entities.ViewInstancePropertyEntity</class>
     <class>org.apache.ambari.server.orm.entities.ViewParameterEntity</class>
     <class>org.apache.ambari.server.orm.entities.ViewResourceEntity</class>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProviderTest.java
index aedac18..1044570 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProviderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProviderTest.java
@@ -281,55 +281,6 @@ public class ViewInstanceResourceProviderTest {
   }
 
 
-  @Test
-  public void testCreateWithShortUrlValidations() throws Exception {
-
-    ViewInstanceResourceProvider provider = new ViewInstanceResourceProvider();
-
-    Set<Map<String, Object>> properties = new HashSet<Map<String, Object>>();
-
-    Map<String, Object> propertyMap = new HashMap<String, Object>();
-
-    propertyMap.put(ViewInstanceResourceProvider.VIEW_NAME_PROPERTY_ID, "V1");
-    propertyMap.put(ViewInstanceResourceProvider.VIEW_VERSION_PROPERTY_ID, "1.0.0");
-    propertyMap.put(ViewInstanceResourceProvider.INSTANCE_NAME_PROPERTY_ID, "I1");
-    propertyMap.put(ViewInstanceResourceProvider.SHORT_URL_PROPERTY_ID, "testUrl");
-
-    properties.add(propertyMap);
-
-    ViewInstanceEntity viewInstanceEntity = new ViewInstanceEntity();
-    viewInstanceEntity.setViewName("V1{1.0.0}");
-    viewInstanceEntity.setName("I1");
-    viewInstanceEntity.setShortUrl("testUrl");
-
-    ViewEntity viewEntity = new ViewEntity();
-    viewEntity.setStatus(ViewDefinition.ViewStatus.DEPLOYED);
-    viewEntity.setName("V1{1.0.0}");
-
-    viewInstanceEntity.setViewEntity(viewEntity);
-
-    expect(viewregistry.duplicatedShortUrl(viewInstanceEntity)).andReturn(true);
-    expect(viewregistry.getDefinition("V1", "1.0.0")).andReturn(viewEntity).anyTimes();
-    expect(viewregistry.getDefinition("V1", null)).andReturn(viewEntity);
-
-    expect(viewregistry.checkAdmin()).andReturn(true);
-
-    replay(viewregistry);
-
-    SecurityContextHolder.getContext().setAuthentication(TestAuthenticationFactory.createAdministrator());
-
-    try {
-      provider.createResources(PropertyHelper.getCreateRequest(properties, null));
-      fail("Expected ResourceAlreadyExistsException.");
-    } catch (ResourceAlreadyExistsException e) {
-      // expected
-    }
-
-    verify(viewregistry);
-
-
-  }
-
 
   @Test
   public void testUpdateResources_viewNotLoaded() throws Exception {

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java b/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java
index ae69589..cdecc4d 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java
@@ -209,9 +209,17 @@ public class UpgradeCatalog240Test {
     Capture<DBAccessor.DBColumnInfo> hostRoleCommandOriginalStartTimeColumnInfo = newCapture();
     dbAccessor.addColumn(eq(UpgradeCatalog240.HOST_ROLE_COMMAND_TABLE), capture(hostRoleCommandOriginalStartTimeColumnInfo));
 
+    Capture<List<DBAccessor.DBColumnInfo>> capturedViewUrlColums = EasyMock.newCapture();
+    dbAccessor.createTable(eq(UpgradeCatalog240.VIEWURL_TABLE), capture(capturedViewUrlColums),eq("url_id"));
+    expect(dbAccessor.getConnection()).andReturn(connection);
+    expect(connection.createStatement()).andReturn(statement);
+
     Capture<DBAccessor.DBColumnInfo> viewInstanceShortUrlInfo = newCapture();
     dbAccessor.addColumn(eq(UpgradeCatalog240.VIEWINSTANCE_TABLE), capture(viewInstanceShortUrlInfo));
 
+    dbAccessor.addFKConstraint(UpgradeCatalog240.VIEWINSTANCE_TABLE, "FK_instance_url_id",
+            UpgradeCatalog240.SHORT_URL_COLUMN, UpgradeCatalog240.VIEWURL_TABLE, "url_id", false);
+
 
     replay(dbAccessor, configuration, connection, statement, resultSet);
 
@@ -348,11 +356,14 @@ public class UpgradeCatalog240Test {
     Assert.assertEquals(Long.class, originalStartTimeInfo.getType());
     Assert.assertEquals(-1L, originalStartTimeInfo.getDefaultValue());
 
-    // Verify host_role_command column
     DBAccessor.DBColumnInfo viewInstanceEntityUrlColInfoValue = viewInstanceShortUrlInfo.getValue();
     Assert.assertNotNull(viewInstanceEntityUrlColInfoValue);
     Assert.assertEquals("short_url", viewInstanceEntityUrlColInfoValue.getName());
-    Assert.assertEquals(String.class, viewInstanceEntityUrlColInfoValue.getType());
+    Assert.assertEquals(Long.class, viewInstanceEntityUrlColInfoValue.getType());
+
+    List<DBAccessor.DBColumnInfo> capturedViewUrlColumsValue = capturedViewUrlColums.getValue();
+    Assert.assertNotNull(capturedViewUrlColumsValue);
+    Assert.assertEquals(capturedViewUrlColumsValue.size(),3);
 
     verify(dbAccessor);
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-views/src/main/java/org/apache/ambari/view/ViewInstanceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-views/src/main/java/org/apache/ambari/view/ViewInstanceDefinition.java b/ambari-views/src/main/java/org/apache/ambari/view/ViewInstanceDefinition.java
index 62f9657..0c3d3ce 100644
--- a/ambari-views/src/main/java/org/apache/ambari/view/ViewInstanceDefinition.java
+++ b/ambari-views/src/main/java/org/apache/ambari/view/ViewInstanceDefinition.java
@@ -61,11 +61,6 @@ public interface ViewInstanceDefinition {
    */
   public String getClusterHandle();
 
-  /**
-   * Get the short URL
-   * @return
-     */
-  public String getShortUrl();
 
   /**
    * Indicates whether or not the view instance should be visible.


[2/2] ambari git commit: AMBARI-15821 Ability to provide logical urls to view instances, Added URL wizard and other UI fixes(Ashwin Rajeev via dipayanb)

Posted by db...@apache.org.
AMBARI-15821 Ability to provide logical urls to view instances, Added URL wizard and other UI fixes(Ashwin Rajeev via dipayanb)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/f62a9277
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/f62a9277
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/f62a9277

Branch: refs/heads/trunk
Commit: f62a927708bddb5f1dffe7342131cb307a550eee
Parents: 614b12f
Author: Dipayan Bhowmick <di...@gmail.com>
Authored: Tue Apr 26 12:33:26 2016 +0530
Committer: Dipayan Bhowmick <di...@gmail.com>
Committed: Tue Apr 26 12:33:26 2016 +0530

----------------------------------------------------------------------
 .../main/resources/ui/admin-web/app/index.html  |   5 +-
 .../ambariViews/CreateViewInstanceCtrl.js       |   5 +
 .../controllers/ambariViews/ViewUrlCtrl.js      | 153 ++++++++
 .../controllers/ambariViews/ViewUrlEditCtrl.js  |  94 +++++
 .../controllers/ambariViews/ViewsEditCtrl.js    |   3 +-
 .../controllers/ambariViews/ViewsListCtrl.js    |  14 +-
 .../ui/admin-web/app/scripts/i18n.config.js     |  26 +-
 .../ui/admin-web/app/scripts/routes.js          |  20 ++
 .../ui/admin-web/app/scripts/services/View.js   | 136 ++++++-
 .../resources/ui/admin-web/app/styles/main.css  |  23 +-
 .../admin-web/app/views/ambariViews/create.html |   8 -
 .../admin-web/app/views/ambariViews/edit.html   |   9 +-
 .../app/views/ambariViews/listTable.html        | 130 +++----
 .../app/views/ambariViews/listUrls.html         |  72 ++++
 .../ui/admin-web/app/views/leftNavbar.html      |  18 +-
 .../ui/admin-web/app/views/urls/create.html     |  51 +++
 .../admin-web/app/views/urls/create_step_1.html |  36 ++
 .../admin-web/app/views/urls/create_step_2.html |  44 +++
 .../admin-web/app/views/urls/create_step_3.html |  60 ++++
 .../ui/admin-web/app/views/urls/edit.html       |  73 ++++
 .../resources/ResourceInstanceFactoryImpl.java  |   4 +
 .../resources/ViewUrlResourceDefinition.java    |  54 +++
 .../server/api/services/ViewUrlsService.java    | 144 ++++++++
 .../internal/DefaultProviderModule.java         |   2 +
 .../internal/ViewInstanceResourceProvider.java  |  23 +-
 .../internal/ViewURLResourceProvider.java       | 358 +++++++++++++++++++
 .../ambari/server/controller/spi/Resource.java  |   2 +
 .../ambari/server/orm/dao/ViewURLDAO.java       | 111 ++++++
 .../server/orm/entities/ViewInstanceEntity.java |  45 ++-
 .../server/orm/entities/ViewURLEntity.java      | 144 ++++++++
 .../server/upgrade/UpgradeCatalog240.java       |  19 +-
 .../apache/ambari/server/view/ViewRegistry.java |  21 +-
 .../view/configuration/InstanceConfig.java      |  15 -
 .../main/resources/Ambari-DDL-Derby-CREATE.sql  |  13 +-
 .../main/resources/Ambari-DDL-MySQL-CREATE.sql  |  12 +-
 .../main/resources/Ambari-DDL-Oracle-CREATE.sql |  12 +-
 .../resources/Ambari-DDL-Postgres-CREATE.sql    |  14 +-
 .../Ambari-DDL-Postgres-EMBEDDED-CREATE.sql     |  13 +-
 .../resources/Ambari-DDL-SQLAnywhere-CREATE.sql |  12 +-
 .../resources/Ambari-DDL-SQLServer-CREATE.sql   |  13 +-
 .../src/main/resources/META-INF/persistence.xml |  19 +-
 .../ViewInstanceResourceProviderTest.java       |  49 ---
 .../server/upgrade/UpgradeCatalog240Test.java   |  15 +-
 .../ambari/view/ViewInstanceDefinition.java     |   5 -
 44 files changed, 1853 insertions(+), 246 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/index.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/index.html b/ambari-admin/src/main/resources/ui/admin-web/app/index.html
index fa911a6..8d7e8e7 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/index.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/index.html
@@ -90,7 +90,7 @@
       </div>
     </div>
 
-    
+
     <!-- build:js scripts/vendor.js -->
     <!-- bower:js -->
     <script src="bower_components/jquery/dist/jquery.js"></script>
@@ -119,6 +119,7 @@
     <script src="bower_components/bootstrap/js/scrollspy.js"></script>
     <script src="bower_components/bootstrap/js/collapse.js"></script>
     <script src="bower_components/bootstrap/js/tab.js"></script>
+
     <!-- endbuild -->
 
     <!-- build:js scripts/main.js -->
@@ -139,6 +140,8 @@
     <script src="scripts/controllers/groups/GroupsEditCtrl.js"></script>
     <script src="scripts/controllers/ambariViews/ViewsListCtrl.js"></script>
     <script src="scripts/controllers/ambariViews/ViewsEditCtrl.js"></script>
+    <script src="scripts/controllers/ambariViews/ViewUrlCtrl.js"></script>
+    <script src="scripts/controllers/ambariViews/ViewUrlEditCtrl.js"></script>
     <script src="scripts/controllers/ambariViews/CreateViewInstanceCtrl.js"></script>
     <script src="scripts/controllers/clusters/ClustersManageAccessCtrl.js"></script>
     <script src="scripts/controllers/clusters/UserAccessListCtrl.js"></script>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/CreateViewInstanceCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/CreateViewInstanceCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/CreateViewInstanceCtrl.js
index 962b795..127bc74 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/CreateViewInstanceCtrl.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/CreateViewInstanceCtrl.js
@@ -198,4 +198,9 @@ angular.module('ambariAdminConsole')
     }
   });
 
+
+
+
+
+
 }]);

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js
new file mode 100644
index 0000000..0cf8d03
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js
@@ -0,0 +1,153 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+angular.module('ambariAdminConsole')
+.controller('ViewUrlCtrl',['$scope', 'View', 'Alert', 'Cluster', '$routeParams', '$location', 'UnsavedDialog', '$translate', function($scope, View, Alert, Cluster, $routeParams, $location, UnsavedDialog, $translate) {
+  var $t = $translate.instant;
+  $scope.form = {};
+  $scope.constants = {
+    props: $t('views.properties')
+  };
+  var targetUrl = '/viewUrls';
+  $scope.url={};
+  $scope.formHolder = {};
+
+  View.getAllVisibleInstance().then(function(views) {
+    var names = [];
+    var instances=[];
+    views.map(function(view){
+      var nameVersion = view.view_name+" {"+view.version+"}";
+        names.push(nameVersion);
+      instances.push({nameV:nameVersion,instance:view.instance_name,cname:view.view_name,version:view.version});
+    });
+
+    var output = [],
+        keys = [];
+
+    angular.forEach(names, function(item) {
+      var key = item;
+      if(keys.indexOf(key) === -1) {
+        keys.push(key);
+        output.push(item);
+      }
+    });
+
+    $scope.viewsVersions =  output;
+    $scope.viewInstances =  instances;
+
+    if($routeParams.viewName && $routeParams.viewVersion && $routeParams.viewInstanceName){
+      var selectedView = $routeParams.viewName+" {"+$routeParams.viewVersion+"}";
+      $scope.url.selectedView = selectedView;
+      $scope.url.selectedInstance = instances.find(function(inst){
+         return inst.nameV === selectedView && inst.instance === $routeParams.viewInstanceName && inst.version === $routeParams.viewVersion && inst.cname === $routeParams.viewName;
+      });
+    }
+
+  }).catch(function(data) {
+    Alert.error($t('views.alerts.cannotLoadViews'), data.data.message);
+  });
+
+  $scope.filterByName = function(nameV){
+    return function (item) {
+      if (item.nameV === nameV)
+      {
+        return true;
+      }
+      return false;
+    };
+  };
+
+  $scope.chomp = function(viewNameVersion){
+    return viewNameVersion.substr(0,viewNameVersion.indexOf("{")).trim();
+  };
+
+
+  $scope.wizardController = function () {
+    var wizard = this;
+
+    //Model
+    wizard.currentStep = 1;
+    wizard.steps = [
+      {
+        step: 1,
+        name: $t('urls.step1'),
+        template: "views/urls/create_step_1.html"
+      },
+      {
+        step: 2,
+        name: $t('urls.step2'),
+        template: "views/urls/create_step_2.html"
+      },
+      {
+        step: 3,
+        name: $t('urls.step3'),
+        template: "views/urls/create_step_3.html"
+      }
+    ];
+    wizard.user = {};
+
+    //Functions
+    wizard.gotoStep = function(newStep) {
+      $scope.formHolder.form.submitted = true;
+      if (newStep < wizard.currentStep || $scope.formHolder.form.$valid) {
+        wizard.currentStep = newStep;
+      }
+    };
+
+    wizard.getStepTemplate = function(){
+      for (var i = 0; i < wizard.steps.length; i++) {
+        if (wizard.currentStep == wizard.steps[i].step) {
+          return wizard.steps[i].template;
+        }
+      }
+    };
+
+    wizard.save = function() {
+      $scope.formHolder.form.submitted = true;
+
+      if($scope.formHolder.form.$valid){
+
+        var payload = {ViewUrlInfo:{
+              url_name:$scope.url.urlName,
+              url_suffix:$scope.url.suffix,
+              view_instance_version:$scope.url.selectedInstance.version,
+              view_instance_name:$scope.url.selectedInstance.instance,
+              view_instance_common_name:$scope.url.selectedInstance.cname
+        }};
+
+        View.updateShortUrl(payload).then(function(urlStatus) {
+          Alert.success($t('urls.urlCreated', {
+            viewName:$scope.url.selectedInstance.cname ,
+            shortUrl:$scope.url.suffix,
+            urlName:$scope.url.urlName
+          }));
+          $scope.formHolder.form.$setPristine();
+          $location.path(targetUrl);
+        }).catch(function(data) {
+          Alert.error($t('views.alerts.cannotLoadViewUrls'), data.message);
+        });
+
+      }
+    };
+  }
+
+
+
+
+}]);

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlEditCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlEditCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlEditCtrl.js
new file mode 100644
index 0000000..93edc69
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlEditCtrl.js
@@ -0,0 +1,94 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+angular.module('ambariAdminConsole')
+.controller('ViewUrlEditCtrl',['$scope', 'View', 'Alert', 'Cluster', '$routeParams', '$location', 'UnsavedDialog', '$translate','ConfirmationModal' ,function($scope, View, Alert, Cluster, $routeParams, $location, UnsavedDialog, $translate,ConfirmationModal) {
+  var $t = $translate.instant;
+  $scope.form = {};
+  $scope.constants = {
+    props: $t('views.properties')
+  };
+  var targetUrl = '/viewUrls';
+
+
+  function setUpEdit(){
+
+      View.getUrlInfo($routeParams.urlName).then(function(url) {
+        $scope.url = url.ViewUrlInfo;
+        $scope.nameVersion = url.ViewUrlInfo.view_instance_common_name +" {" + url.ViewUrlInfo.view_instance_version +"}"
+      }).catch(function(data) {
+        Alert.error($t('views.alerts.cannotLoadViewUrl'), data.data.message);
+      });
+  }
+
+  setUpEdit();
+
+
+  $scope.updateUrl = function() {
+      $scope.url_form.submitted = true;
+
+      if($scope.url_form.$valid){
+
+          var payload = {ViewUrlInfo:{
+              url_name:$scope.url.url_name,
+              url_suffix:$scope.url.url_suffix,
+              view_instance_version:'',
+              view_instance_name:'',
+              view_instance_common_name:''
+          }};
+
+          View.editShortUrl(payload).then(function(urlStatus) {
+              Alert.success($t('urls.urlUpdated', {
+                  viewName:$scope.url.view_instance_common_name ,
+                  shortUrl:$scope.url.suffix,
+                  urlName:$scope.url.url_name
+              }));
+              $scope.url_form.$setPristine();
+              $location.path(targetUrl);
+          }).catch(function(data) {
+              Alert.error($t('views.alerts.cannotLoadViewUrls'), data.data.message);
+          });
+
+      }
+  };
+
+
+    $scope.deleteUrl = function() {
+
+        ConfirmationModal.show(
+            $t('common.delete', {
+                term: $t('urls.url')
+            }),
+            $t('common.deleteConfirmation', {
+                instanceType: $t('urls.url').toLowerCase(),
+                instanceName: '"' + $scope.url.url_name + '"'
+            })
+        ).then(function() {
+            View.deleteUrl($scope.url.url_name).then(function() {
+                $location.path(targetUrl);
+            });
+        });
+
+
+
+    };
+
+
+
+}]);

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsEditCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsEditCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsEditCtrl.js
index d46a30f..877e230 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsEditCtrl.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsEditCtrl.js
@@ -244,8 +244,7 @@ angular.module('ambariAdminConsole')
         'ViewInstanceInfo':{
           'visible': $scope.settings.visible,
           'label': $scope.settings.label,
-          'description': $scope.settings.description,
-          'short_url': $scope.settings.shortUrl
+          'description': $scope.settings.description
         }
       };
       return View.updateInstance($routeParams.viewId, $routeParams.version, $routeParams.instanceId, data)

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
index 6d1dc52..ed389e1 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
@@ -18,7 +18,7 @@
 'use strict';
 
 angular.module('ambariAdminConsole')
-.controller('ViewsListCtrl',['$scope', 'View', '$modal', 'Alert', 'ConfirmationModal', '$location', '$translate', function($scope, View, $modal, Alert, ConfirmationModal, $location, $translate) {
+.controller('ViewsListCtrl',['$scope', 'View','$modal', 'Alert', 'ConfirmationModal', '$location', '$translate', function($scope, View, $modal, Alert, ConfirmationModal, $location, $translate) {
   var deferredList = [],
     $t = $translate.instant;
   $scope.constants = {
@@ -128,6 +128,18 @@ angular.module('ambariAdminConsole')
 
   $scope.reloadViews = function () {
     loadViews();
+  };
+
+
+  $scope.listViewUrls = function(){
+    View.allUrls().then(function(urls) {
+      $scope.urls = urls;
+      $scope.ViewNameFilterOptions = urls.items.map(function(url){
+        return url.ViewUrlInfo.view_instance_common_name;
+      });
+    }).catch(function(data) {
+      Alert.error($t('views.alerts.cannotLoadViewUrls'), data.data.message);
+    });
   }
 
 }]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
index 68a4bec..3e475d9 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
@@ -32,6 +32,7 @@ angular.module('ambariAdminConsole')
       'signOut': 'Sign out',
       'clusters': 'Clusters',
       'views': 'Views',
+      'viewUrls': 'View URL\'s',
       'roles': 'Roles',
       'users': 'Users',
       'groups': 'Groups',
@@ -132,7 +133,9 @@ angular.module('ambariAdminConsole')
         'cannotLoadClusterStatus': 'Cannot load cluster status',
         'clusterRenamed': 'The cluster has been renamed to {{clusterName}}.',
         'cannotRenameCluster': 'Cannot rename cluster to {{clusterName}}',
-        'unsavedChanges': 'You have unsaved changes. Save changes or discard?'
+        'tooShort': 'URL is too short',
+        'tooLong': 'URL is too long',
+        'onlyText': 'You can add only text urls in the lower case'
       }
     },
 
@@ -202,6 +205,8 @@ angular.module('ambariAdminConsole')
       'pending': 'Pending...',
       'deploying': 'Deploying...',
       'properties': 'properties',
+      'urlCreate':'Create new URL',
+      'urlDelete':'Delete URL',
 
       'alerts': {
         'noSpecialChars': 'Must not contain any special characters.',
@@ -222,10 +227,27 @@ angular.module('ambariAdminConsole')
         'cannotSaveSettings': 'Cannot save settings',
         'cannotSaveProperties': 'Cannot save properties',
         'cannotDeleteInstance': 'Cannot delete instance',
-        'cannotLoadViews': 'Cannot load views'
+        'cannotLoadViews': 'Cannot load views',
+        'cannotLoadViewUrls': 'Cannot load view URL\'s',
+        'cannotLoadViewUrl': 'Cannot load view URL'
       }
     },
 
+    'urls':{
+      'name':'Name',
+      'url':'URL',
+      'create':'Create',
+      'edit':'Edit',
+      'view':'View',
+      'viewInstance':'Instance',
+      'step1':'Create URL',
+      'step2':'Select instance',
+      'step3':'Assign URL',
+      'noUrlsToDisplay':'No short URL\'s configured',
+      'urlCreated':'Created short URL <a href="/#/main/view/{{viewName}}/{{shortUrl}}">{{urlName}}</a>',
+      'urlUpdated':'Updated short URL <a href="/#/main/view/{{viewName}}/{{shortUrl}}">{{urlName}}</a>'
+    },
+
     'clusters': {
       'switchToList': 'Switch&nbsp;to&nbsp;list&nbsp;view',
       'switchToBlock': 'Switch&nbsp;to&nbsp;block&nbsp;view',

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js
index 4fc4ea6..0566969 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js
@@ -88,6 +88,26 @@ angular.module('ambariAdminConsole')
       templateUrl: 'views/ambariViews/listTable.html',
       controller: 'ViewsListCtrl'
     },
+    listViewUrls: {
+      url: '/viewUrls',
+      templateUrl: 'views/ambariViews/listUrls.html',
+      controller: 'ViewsListCtrl'
+    },
+    createViewUrl:{
+      url: '/urls/new',
+      templateUrl: 'views/urls/create.html',
+      controller: 'ViewUrlCtrl'
+    },
+    linkViewUrl:{
+      url: '/urls/link/:viewName/:viewVersion/:viewInstanceName',
+      templateUrl: 'views/urls/create.html',
+      controller: 'ViewUrlCtrl'
+    },
+    editViewUrl:{
+      url: '/urls/edit/:urlName',
+      templateUrl: 'views/urls/edit.html',
+      controller: 'ViewUrlEditCtrl'
+    },
     edit: {
       url: '/views/:viewId/versions/:version/instances/:instanceId/edit',
       templateUrl: 'views/ambariViews/edit.html',

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js
index cbe11e4..36bd32d 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js
@@ -24,6 +24,8 @@ angular.module('ambariAdminConsole')
     angular.extend(this, item);
   }
 
+
+
   ViewInstance.find = function(viewName, version, instanceName) {
     var deferred = $q.defer();
     var fields = [
@@ -51,6 +53,114 @@ angular.module('ambariAdminConsole')
   };
 
 
+  function ViewUrl(item) {
+    angular.extend(this, item);
+  }
+
+  function URLStatus(item){
+    angular.element(this,item);
+  }
+
+  ViewUrl.all = function() {
+    var deferred = $q.defer();
+
+    $http({
+      method: 'GET',
+      dataType: "json",
+      url: Settings.baseUrl + '/view/urls',
+
+    })
+        .success(function(data) {
+          deferred.resolve(new ViewUrl(data));
+        })
+        .error(function(data) {
+          deferred.reject(data);
+        });
+
+    return deferred.promise;
+  };
+
+
+  ViewUrl.updateShortUrl = function(payload){
+    var deferred = $q.defer();
+
+    $http({
+      method: 'POST',
+      dataType: "json",
+      url: Settings.baseUrl + '/view/urls/'+payload.ViewUrlInfo.url_name,
+      data:payload
+    })
+        .success(function(data) {
+          deferred.resolve(new URLStatus(data));
+        })
+        .error(function(data) {
+          deferred.reject(data);
+        });
+
+    return deferred.promise;
+  };
+
+  ViewUrl.deleteUrl = function(urlName){
+    var deferred = $q.defer();
+
+    $http({
+      method: 'DELETE',
+      dataType: "json",
+      url: Settings.baseUrl + '/view/urls/'+ urlName,
+    })
+        .success(function(data) {
+          deferred.resolve(new URLStatus(data));
+        })
+        .error(function(data) {
+          deferred.reject(data);
+        });
+
+    return deferred.promise;
+  };
+
+
+  ViewUrl.editShortUrl = function(payload){
+    var deferred = $q.defer();
+
+    $http({
+      method: 'PUT',
+      dataType: "json",
+      url: Settings.baseUrl + '/view/urls/'+payload.ViewUrlInfo.url_name,
+      data:payload
+    })
+        .success(function(data) {
+          deferred.resolve(new URLStatus(data));
+        })
+        .error(function(data) {
+          deferred.reject(data);
+        });
+
+    return deferred.promise;
+  };
+
+
+  ViewUrl.urlInfo =  function(urlName){
+
+    var deferred = $q.defer();
+
+    $http({
+      method: 'GET',
+      dataType: "json",
+      url: Settings.baseUrl + '/view/urls/'+urlName,
+
+    })
+        .success(function(data) {
+          deferred.resolve(new ViewUrl(data));
+        })
+        .error(function(data) {
+          deferred.reject(data);
+        });
+
+    return deferred.promise;
+  };
+
+
+
   function View(item){
     var self = this;
     self.view_name = item.ViewInfo.view_name;
@@ -79,6 +189,27 @@ angular.module('ambariAdminConsole')
     return ViewInstance.find(viewName, version, instanceName);
   };
 
+  View.allUrls =  function(){
+    return ViewUrl.all()
+  };
+
+  View.getUrlInfo = function(urlName){
+    return ViewUrl.urlInfo(urlName);
+  };
+
+  View.deleteUrl = function(urlName){
+    return ViewUrl.deleteUrl(urlName);
+  };
+
+
+  View.updateShortUrl = function(payload){
+    return ViewUrl.updateShortUrl(payload);
+  };
+
+  View.editShortUrl = function(payload){
+    return ViewUrl.editShortUrl(payload);
+  };
+
   View.deleteInstance = function(viewName, version, instanceName) {
     return $http.delete(Settings.baseUrl +'/views/'+viewName+'/versions/'+version+'/instances/'+instanceName, {
       headers: {
@@ -168,8 +299,7 @@ angular.module('ambariAdminConsole')
         visible: instanceInfo.visible,
         icon_path: instanceInfo.icon_path,
         icon64_path: instanceInfo.icon64_path,
-        description: instanceInfo.description,
-        short_url:instanceInfo.shortUrl
+        description: instanceInfo.description
       };
 
     angular.forEach(instanceInfo.properties, function(property) {
@@ -308,7 +438,7 @@ angular.module('ambariAdminConsole')
       url: Settings.baseUrl + '/views',
       params:{
         'fields': fields.join(','),
-        'versions/ViewVersionInfo/system': false
+        'versions/ViewVersionInfo/system' : false
       }
     }).success(function(data) {
       var views = [];

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css b/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
index edf8524..b55d89c 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
@@ -97,7 +97,7 @@
   -webkit-transform: rotateX(0deg);
   -ms-transform: rotateX(0deg);
   -o-transform: rotateX(0deg);
-  transform: rotateX(0deg); 
+  transform: rotateX(0deg);
 }
 .editable-list-container .actions-panel.ng-hide{
   -webkit-transform: rotateX(90deg);
@@ -215,6 +215,10 @@
   display: inline-block;
  }
 
+ .small-input{
+   max-width: 300px;
+ }
+
 .paginator{
   margin: 0;
 }
@@ -471,7 +475,7 @@ a.gotoinstance{
   display: block;
   float: left;
   text-decoration: none;
-  
+
 }
 #top-nav .navbar.navbar-static-top .brand.cluster-name{
   margin-left: 10px;
@@ -487,7 +491,7 @@ a.gotoinstance{
 }
 .create-view-form .description h4 span{
   font-weight: normal;
-  
+
 }
 .create-view-form .view-header{
 }
@@ -553,7 +557,7 @@ a.gotoinstance{
   top: 5px;
   right: 50px;
   z-index: 10;
-} 
+}
 .views-list-pane .search-container .close{
   right: 50px;
   top: 5px;
@@ -629,6 +633,9 @@ ul.nav li > a{
 .padding-left-30{
   padding-left: 30px;
 }
+.padding-bottom-30{
+  padding-bottom: 30px;
+}
 .no-margin-bottom{
   margin-bottom: 0!important;
 }
@@ -1136,7 +1143,7 @@ button.btn.btn-xs{
   -ms-transform: translateX(1000px);
   -o-transform: translateX(1000px);
   transform: translateX(1000px);
-  
+
   padding: 0;
   margin: 0;
   max-height: 0;
@@ -1260,15 +1267,15 @@ button.btn.btn-xs{
 }
 
 @-webkit-keyframes stretchdelay {
-  0%, 40%, 100% { -webkit-transform: scaleY(0.4) }  
+  0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
   20% { -webkit-transform: scaleY(1.0) }
 }
 
 @keyframes stretchdelay {
-  0%, 40%, 100% { 
+  0%, 40%, 100% {
     transform: scaleY(0.4);
     -webkit-transform: scaleY(0.4);
-  }  20% { 
+  }  20% {
     transform: scaleY(1.0);
     -webkit-transform: scaleY(1.0);
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/create.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/create.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/create.html
index 20ccadb..eaff70b 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/create.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/create.html
@@ -77,14 +77,6 @@
         </div>
       </div>
 
-      <div class="form-group" ng-class="{'has-error' : form.instanceCreateForm.submitted }">
-        <label for="" class="control-label col-sm-3">{{'views.shortUrl' | translate}}</label>
-        <div class="col-sm-9">
-          <input type="text" class="form-control" name="short_url" ng-model="instance.shortUrl" maxlength="200">
-        </div>
-      </div>
-
-
       <div class="form-group">
         <div class="col-sm-10 col-sm-offset-3">
           <div class="checkbox">

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/edit.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/edit.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/edit.html
index b41abc8..8eff030 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/edit.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/edit.html
@@ -78,14 +78,13 @@
           </div>
         </div>
 
-
-        <div class="form-group" ng-class="{'has-error' : form.instanceCreateForm.submitted }">
+        <div class="form-group">
           <label for="" class="control-label col-sm-3">{{'views.shortUrl' | translate}}</label>
           <div class="col-sm-9">
-            <input type="text" class="form-control" name="short_url" ng-model="settings.shortUrl" maxlength="200">
-
+            <p class="form-control-static"><a href="/#/main/view/{{instance.ViewInstanceInfo.view_name}}/{{settings.shortUrl}}" ng-if="settings.shortUrl">{{settings.shortUrl}}</a></p>
+            <a ng-if="!settings.shortUrl" href="#/urls/link/{{instance.ViewInstanceInfo.view_name}}/{{instance.ViewInstanceInfo.version}}/{{instance.ViewInstanceInfo.instance_name}}" class="btn btn-primary createuser-btn"><span class="glyphicon glyphicon-plus"></span> {{'views.urlCreate' | translate}}</a>
+          </div>
           </div>
-        </div>
 
 
         <div class="form-group">

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listTable.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listTable.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listTable.html
index ae71d78..906eef5 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listTable.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listTable.html
@@ -15,87 +15,87 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 -->
-    
+
 <div class="views-list-table">
-  <div class="clearfix">
-    <ol class="breadcrumb pull-left">
-      <li class="active">{{'common.views' | translate}}</li>
-      <button ng-click="reloadViews()"
-              class="btn btn-xs">
-        <i class="glyphicon glyphicon-refresh"></i>
-      </button>
-    </ol>
-    <div class="pull-right col-sm-4">
-      <div class="input-group search-container">
-        <input type="text" class="form-control search-input" placeholder="{{'common.search' | translate}}" ng-model="viewsFilter" ng-change="getFilteredViews()">
-        <button type="button" class="close clear-search" ng-show="viewsFilter" ng-click="viewsFilter=''; getFilteredViews()"><span aria-hidden="true">&times;</span><span class="sr-only">{{"common.controls.close" | translate}}</span></button>
+    <div class="clearfix">
+        <ol class="breadcrumb pull-left">
+            <li class="active">{{'common.views' | translate}}</li>
+            <button ng-click="reloadViews()"
+                    class="btn btn-xs">
+                <i class="glyphicon glyphicon-refresh"></i>
+            </button>
+        </ol>
+        <div class="pull-right col-sm-4">
+            <div class="input-group search-container">
+                <input type="text" class="form-control search-input" placeholder="{{'common.search' | translate}}" ng-model="viewsFilter" ng-change="getFilteredViews()">
+                <button type="button" class="close clear-search" ng-show="viewsFilter" ng-click="viewsFilter=''; getFilteredViews()"><span aria-hidden="true">&times;</span><span class="sr-only">{{"common.controls.close" | translate}}</span></button>
         <span class="input-group-addon">
           <span class="glyphicon glyphicon-search"></span>
         </span>
-      </div>
+            </div>
+        </div>
     </div>
-  </div>
-  <hr>
-  <div class="row">
-    <div class="col-sm-3 padding-left-30"><h4>{{'views.viewName' | translate}}</h4></div>
-    <div class="col-sm-3"><h4>{{'views.instances' | translate}}</h4></div>
-    <div class="col-sm-6"><h4></h4></div>
-  </div>
-  <accordion close-others="false">
-    <accordion-group ng-repeat="view in filteredViews" is-open="view.isOpened">
-      <accordion-heading>
-        <div class="row">
-          <div class="col-sm-3">
-            <i class="glyphicon glyphicon-chevron-right" ng-class="{'opened': view.isOpened}"></i>
-            {{view.view_name}}
-          </div>
-          <div class="col-sm-3">
+    <hr>
+    <div class="row">
+        <div class="col-sm-3 padding-left-30"><h4>{{'views.viewName' | translate}}</h4></div>
+        <div class="col-sm-3"><h4>{{'views.instances' | translate}}</h4></div>
+        <div class="col-sm-6"><h4></h4></div>
+    </div>
+    <accordion close-others="false">
+        <accordion-group ng-repeat="view in filteredViews" is-open="view.isOpened">
+            <accordion-heading>
+                <div class="row">
+                    <div class="col-sm-3">
+                        <i class="glyphicon glyphicon-chevron-right" ng-class="{'opened': view.isOpened}"></i>
+                        {{view.view_name}}
+                    </div>
+                    <div class="col-sm-3">
             <span ng-repeat="(version, vData) in view.versions">
               {{version}}
                 <span ng-switch="vData.status">
                   <span ng-switch-when="PENDING" class="viewstatus pending" ng-switch-when="true" tooltip="{{'views.pending' | translate}}"></span>
                   <div class="viewstatus deploying" ng-switch-when="DEPLOYING" tooltip="{{'views.deploying' | translate}}">
-                    <div class="rect1"></div>
-                    <div class="rect2"></div>
-                    <div class="rect3"></div>
+                      <div class="rect1"></div>
+                      <div class="rect2"></div>
+                      <div class="rect3"></div>
                   </div>
                   <span ng-switch-when="DEPLOYED">({{vData.count}})</span>
                   <span ng-switch-when="ERROR" tooltip="{{'views.alerts.deployError' | translate}}"><i class="fa fa-exclamation-triangle"></i></span>
                 </span>
               {{$last ? '' : ', '}}
             </span>
-          </div>
-          <div class="col-sm-6">{{view.description}}</div>
+                    </div>
+                    <div class="col-sm-6">{{view.description}}</div>
+                </div>
+            </accordion-heading>
+            <table class="table instances-table">
+                <tbody>
+                <tr ng-repeat="instance in view.instances">
+                    <td class="col-sm-1"></td>
+                    <td class="col-sm-5">
+                        <a href="#/views/{{view.view_name}}/versions/{{instance.ViewInstanceInfo.version}}/instances/{{instance.ViewInstanceInfo.instance_name}}/edit" class="instance-link">{{instance.label}}</a>
+                    </td>
+                    <td class="col-sm-5">{{instance.ViewInstanceInfo.version}}</td>
+                    <td class="col-sm-5 " ><div class="description-column" tooltip="{{instance.ViewInstanceInfo.description}}">{{instance.ViewInstanceInfo.description || 'No description'}}</div>
+                    </td>
+                </tr>
+                </tbody>
+                <tfoot>
+                <tr>
+                    <td class="col-sm-3"></td>
+                    <td class="col-sm-3">
+                        <a tooltip="{{view.canCreateInstance ? '' : constants.unable}}" class="btn btn-default createisntance-btn {{view.canCreateInstance ? '' : 'disabled'}}" href ng-click="gotoCreate(view.view_name, view.canCreateInstance);"><span class="glyphicon glyphicon-plus"></span> {{'views.create' | translate}}</a>
+                    </td>
+                    <td class="col-sm-3"></td>
+                    <td class="col-sm-3">
+                    </td>
+                </tr>
+                </tfoot>
+            </table>
+        </accordion-group>
+        <div class="alert alert-info" ng-show="views && !filteredViews.length">
+            {{'common.alerts.nothingToDisplay' | translate: '{term: constants.views}'}}
         </div>
-      </accordion-heading>
-      <table class="table instances-table">
-        <tbody>
-          <tr ng-repeat="instance in view.instances">
-            <td class="col-sm-1"></td>
-            <td class="col-sm-5">
-              <a href="#/views/{{view.view_name}}/versions/{{instance.ViewInstanceInfo.version}}/instances/{{instance.ViewInstanceInfo.instance_name}}/edit" class="instance-link">{{instance.label}}</a>
-            </td>
-            <td class="col-sm-5">{{instance.ViewInstanceInfo.version}}</td>
-            <td class="col-sm-5 " ><div class="description-column" tooltip="{{instance.ViewInstanceInfo.description}}">{{instance.ViewInstanceInfo.description || 'No description'}}</div>
-            </td>
-          </tr>
-        </tbody>
-        <tfoot>
-          <tr>
-            <td class="col-sm-3"></td>
-            <td class="col-sm-3">
-              <a tooltip="{{view.canCreateInstance ? '' : constants.unable}}" class="btn btn-default createisntance-btn {{view.canCreateInstance ? '' : 'disabled'}}" href ng-click="gotoCreate(view.view_name, view.canCreateInstance);"><span class="glyphicon glyphicon-plus"></span> {{'views.create' | translate}}</a>
-            </td>
-            <td class="col-sm-3"></td>
-            <td class="col-sm-3">
-            </td>
-          </tr>
-        </tfoot>
-      </table>
-    </accordion-group>
-    <div class="alert alert-info" ng-show="views && !filteredViews.length">
-      {{'common.alerts.nothingToDisplay' | translate: '{term: constants.views}'}}
-    </div>
 
-  </accordion>
+    </accordion>
 </div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html
new file mode 100644
index 0000000..a2949f9
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html
@@ -0,0 +1,72 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+
+<div class="views-list-table" data-ng-init="listViewUrls()">
+
+
+    <div class="clearfix">
+        <ol class="breadcrumb pull-left">
+            <li class="active">{{'common.viewUrls' | translate}}</li>
+        </ol>
+        <div class="pull-right top-margin-4">
+            <link-to route="views.createViewUrl" class="btn btn-primary createuser-btn"><span class="glyphicon glyphicon-plus"></span> {{'views.urlCreate' | translate}}</link-to>
+        </div>
+    </div>
+    <hr>
+    <table class="table table-striped table-hover">
+        <thead>
+        <tr>
+
+            <th>
+               <span class="padding-bottom-30">{{'urls.name' | translate}}</span>
+            </th>
+            <th>
+                <span class="padding-bottom-30">{{'urls.url' | translate}}</span>
+            </th>
+            <th>
+                <span class="padding-bottom-30">{{'urls.view' | translate}}</span>
+            </th>
+            <th>
+                <span class="padding-bottom-30">{{'urls.viewInstance' | translate}}</span>
+            </th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="url in urls.items">
+
+            <td>
+                <a href="#/urls/edit/{{url.ViewUrlInfo.url_name}}">{{url.ViewUrlInfo.url_name}}</a>
+            </td>
+            <td>
+                <a href="/#/main/view/{{url.ViewUrlInfo.view_instance_common_name}}/{{url.ViewUrlInfo.url_suffix}}">/main/view/{{url.ViewUrlInfo.view_instance_common_name}}/{{url.ViewUrlInfo.url_suffix}}</a>
+            </td>
+            <td>
+                <span>{{url.ViewUrlInfo.view_instance_common_name}} {{"{"+url.ViewUrlInfo.view_instance_version+"}"}} </span>
+            </td>
+            <td>
+                <span>{{url.ViewUrlInfo.view_instance_name}}</span>
+            </td>
+
+        </tr>
+        </tbody>
+    </table>
+    <div class="alert alert-info col-sm-12" ng-show="!urls.items.length">
+        {{'urls.noUrlsToDisplay'| translate}}
+    </div>
+
+</div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html
index 9bc54ff..b155041 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html
@@ -22,19 +22,19 @@
       <div class="cluster-section" ng-show="cluster">
         <div id="cluster-name"  ng-switch on="editCluster.editingName">
           <h5 ng-switch-when="false"><div title={{cluster.Clusters.cluster_name}} class="clusterDisplayName">{{clusterDisplayName()}}</div>
-            <i ng-show="cluster.Clusters.provisioning_state == 'INSTALLED'" 
-               ng-click="toggleEditName()" 
+            <i ng-show="cluster.Clusters.provisioning_state == 'INSTALLED'"
+               ng-click="toggleEditName()"
                class="glyphicon glyphicon-edit pull-right edit-cluster-name renameCluster" tooltip="{{'common.renameCluster' | translate}}">
             </i>
           </h5>
 
-          <form ng-keyup="toggleEditName($event)" 
-                tabindex="1" 
-                name="editClusterNameForm" 
-                class="editClusterNameForm" 
+          <form ng-keyup="toggleEditName($event)"
+                tabindex="1"
+                name="editClusterNameForm"
+                class="editClusterNameForm"
                 ng-switch-when="true"
                 ng-submit="editCluster.name !== cluster.Clusters.cluster_name && editClusterNameForm.newClusterName.$valid && confirmClusterNameChange()">
-            <div class="form-group" 
+            <div class="form-group"
                  ng-class="{'has-error': editClusterNameForm.newClusterName.$invalid && !editClusterNameForm.newClusterName.$pristine }">
               <input
                   autofocus
@@ -91,10 +91,12 @@
     <div class="panel-body">
       <ul class="nav nav-pills nav-stacked">
         <li ng-class="{active: isActive('views.list')}"><link-to route="views.list" class="viewslist-link">{{'common.views' | translate}}</link-to></li>
+        <li ng-class="{active: isActive('views.listViewUrls')}"><link-to route="views.listViewUrls" class="viewsUrlList-link">{{'common.viewUrls' | translate}}</link-to></li>
       </ul>
     </div>
   </div>
 
+
   <div class="panel panel-default">
     <div class="panel-heading"><span class="glyphicon glyphicon-user"></span> {{'common.userGroupManagement' | translate}}</div>
     <div class="panel-body">
@@ -115,4 +117,4 @@
     </div>
   </div>
 </div>
-  
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html
new file mode 100644
index 0000000..eb31b42
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html
@@ -0,0 +1,51 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+<ol class="breadcrumb">
+    <li><a href="#/users">{{'urls.url' | translate}}</a></li>
+    <li class="active">{{'urls.create' | translate}}</li>
+</ol>
+<hr>
+    <div class="row">
+        <div class="col-sm-10">
+
+            <div id="wizard-container" ng-controller="wizardController as wizard">
+
+                <div id="wizard-step-container" class="bottom-margin">
+                    <ul class="nav nav-pills nav-justified">
+                        <li ng-repeat="step in wizard.steps" ng-class="{'active':step.step == wizard.currentStep}"><a ng-click="wizard.gotoStep(step.step)" href="">{{step.name}}</a></li>
+                    </ul>
+                </div>
+
+                <div id="wizard-content-container">
+                    <ng-include src="wizard.getStepTemplate()"></ng-include>
+                </div>
+
+                <div id="wizard-navigation-container">
+                    <div class="pull-right">
+                <span class="btn-group">
+                  <button ng-disabled="wizard.currentStep <= 1" class="btn btn-default" name="previous" type="button" ng-click="wizard.gotoStep(wizard.currentStep - 1)"><i class="fa fa-arrow-left"></i> Previous step</button>
+                  <button ng-disabled="wizard.currentStep >= wizard.steps.length" class="btn btn-primary" name="next" type="button" ng-click="wizard.gotoStep(wizard.currentStep + 1)">Next step <i class="fa fa-arrow-right"></i></button>
+                </span>
+                        <button ng-disabled="wizard.currentStep != wizard.steps.length" class="btn btn-success" name="next" type="button" ng-click="wizard.save()"> <i class="fa fa-floppy-o"></i> Save</button>
+                    </div>
+                </div>
+
+            </div>
+
+        </div>
+</div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html
new file mode 100644
index 0000000..e19313e
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html
@@ -0,0 +1,36 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+
+<form ng-controller="wizardController as wizard" class="form-horizontal create-user-form" role="form" novalidate name="formHolder.form" autocomplete="off">
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
+    <div class="col-sm-10">
+      <input type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.urlName" required autocomplete="off">
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
+    <div class="col-sm-10">
+      <select class="form-control" id="urlselect" name="url_view_name" ng-options="version for version in viewsVersions" ng-model="url.selectedView" required></select>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+</form>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html
new file mode 100644
index 0000000..36bce88
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html
@@ -0,0 +1,44 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+<form  class="form-horizontal create-user-form" role="form" novalidate name="formHolder.form" autocomplete="off">
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
+    <div class="col-sm-10">
+      <input disabled type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.urlName" required autocomplete="off">
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
+    <div class="col-sm-10">
+      <select class="form-control" disabled id="urlselect" name="url_view_name" ng-options="version for version in viewsVersions" ng-model="url.selectedView" required></select>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlinstanceselect" class="col-sm-2 control-label">{{'urls.viewInstance' | translate}}</label>
+    <div class="col-sm-10">
+      <select class="form-control" id="urlinstanceselect" name="url_view_instance_name" ng-options="instance.instance for instance in viewInstances | filter:filterByName(url.selectedView)" ng-model="url.selectedInstance" required></select>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+</form>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html
new file mode 100644
index 0000000..e6b3c1c
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html
@@ -0,0 +1,60 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+<form  class="form-horizontal create-user-form" role="form" novalidate name="formHolder.form" autocomplete="off">
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
+    <div class="col-sm-10">
+      <input disabled type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.urlName" required autocomplete="off">
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
+    <div class="col-sm-10">
+      <select class="form-control" disabled id="urlselect" name="url_view_name" ng-options="version for version in viewsVersions" ng-model="url.selectedView" required></select>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlinstanceselect" class="col-sm-2 control-label">{{'urls.viewInstance' | translate}}</label>
+    <div class="col-sm-10">
+      <select class="form-control" disabled id="urlinstanceselect" name="url_view_instance_name" ng-options="instance.instance for instance in viewInstances | filter:filterByName(url.selectedView)" ng-model="url.selectedInstance" required></select>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_suffix.$error.required  && formHolder.form.submitted}">
+    <label for="urlsuffixin" class="col-sm-2 control-label">{{'views.shortUrl' | translate}}</label>
+    <div class="col-sm-10">
+      <div class="input-group">
+      <span id="basic-addon1" class="input-group-addon">/main/view/{{chomp(url.selectedView)}}/</span><input aria-describedby="basic-addon1" type="text" class="form-control" id="urlsuffixin" name="url_view_suffix" placeholder="{{'views.shortUrl' | translate}}" ng-model="url.suffix" ng-pattern="/[a-z]+/" ng-minlength="3" ng-maxlength="10" required autocomplete="off">
+        </div>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.required   && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.minlength   && formHolder.form.submitted">{{'common.alerts.tooShort' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.maxlength   && formHolder.form.submitted">{{'common.alerts.tooLong' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.pattern   && formHolder.form.submitted">{{'common.alerts.onlyText' | translate}}</div>
+
+    </div>
+  </div>
+
+</form>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/edit.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/edit.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/edit.html
new file mode 100644
index 0000000..de7722e
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/edit.html
@@ -0,0 +1,73 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+<ol class="breadcrumb">
+  <li><a href="#/users">{{'urls.url' | translate}}</a></li>
+  <li class="active">{{'urls.edit' | translate}}</li>
+  <div class="pull-right top-margin-4">
+    <button class="btn deleteuser-btn btn-danger" ng-click="deleteUrl()">{{'views.urlDelete' | translate}}</button>
+  </div>
+</ol>
+<hr>
+
+
+<form  class="form-horizontal create-user-form" role="form" novalidate name="url_form" autocomplete="off">
+  <div class="form-group" ng-class="{'has-error' : url_form.url_name.$error.required  && url_form.submitted}">
+    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
+    <div class="col-sm-10">
+      <input disabled type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.url_name" required autocomplete="off">
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_name.$error.required  && url_form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+  <div class="form-group" ng-class="{'has-error' : url_form.url_view_name.$error.required  && url_form.submitted}">
+    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
+    <div class="col-sm-10">
+      <input class="form-control" disabled id="urlselect" name="url_view_name" ng-model="nameVersion" required>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_name.$error.required  && url_form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+  <div class="form-group" ng-class="{'has-error' : url_form.url_view_instance_name.$error.required  && url_form.submitted}">
+    <label for="urlinstanceselect" class="col-sm-2 control-label">{{'urls.viewInstance' | translate}}</label>
+    <div class="col-sm-10">
+      <input class="form-control" disabled id="urlinstanceselect" name="url_view_instance_name"  ng-model="url.view_instance_name" required>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_instance_name.$error.required  && url_form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+
+  <div class="form-group" ng-class="{'has-error' : url_form.url_view_suffix.$error.required  && url_form.submitted}">
+    <label for="urlsuffixin" class="col-sm-2 control-label">{{'views.shortUrl' | translate}}</label>
+    <div class="col-sm-10">
+      <div class="input-group">
+      <span id="basic-addon1" class="input-group-addon">/main/view/{{url.view_instance_common_name}}/</span><input aria-describedby="basic-addon1" type="text" class="form-control" id="urlsuffixin" name="url_view_suffix" placeholder="{{'views.shortUrl' | translate}}" ng-model="url.url_suffix" ng-pattern="/[a-z]+/" ng-minlength="3" ng-maxlength="10" required autocomplete="off">
+        </div>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_suffix.$error.required   && url_form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_suffix.$error.minlength   && url_form.submitted">{{'common.alerts.tooShort' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_suffix.$error.maxlength   && url_form.submitted">{{'common.alerts.tooLong' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_suffix.$error.pattern   && url_form.submitted">{{'common.alerts.onlyText' | translate}}</div>
+
+    </div>
+  </div>
+  <div class="pull-right">
+    <button ng-disabled="wizard.currentStep != wizard.steps.length" class="btn btn-success" name="update_url_button" type="button" ng-click="updateUrl()"> <i class="fa fa-edit"></i> Update</button>
+  </div>
+
+</form>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
index eed2703..0b77511 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
@@ -237,6 +237,10 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
         resourceDefinition = new ViewInstanceResourceDefinition(subResourceDefinitions);
         break;
 
+      case ViewURL:
+        resourceDefinition = new ViewUrlResourceDefinition();
+        break;
+
       case Blueprint:
         resourceDefinition = new BlueprintResourceDefinition();
         break;

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ViewUrlResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ViewUrlResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ViewUrlResourceDefinition.java
new file mode 100644
index 0000000..d2c7b62
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ViewUrlResourceDefinition.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.api.resources;
+
+import org.apache.ambari.server.controller.spi.Resource;
+
+import java.util.Collections;
+import java.util.Set;
+
+
+/**
+ * View resource definition.
+ */
+public class ViewUrlResourceDefinition extends BaseResourceDefinition {
+
+  // ----- Constructors ------------------------------------------------------
+
+  /**
+   * Construct a view resource definition.
+   */
+  public ViewUrlResourceDefinition() {
+    super(Resource.Type.ViewURL);
+  }
+
+
+  // ----- ResourceDefinition ------------------------------------------------
+
+  @Override
+  public String getPluralName() {
+    return "view_urls";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "view_url";
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/api/services/ViewUrlsService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ViewUrlsService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ViewUrlsService.java
new file mode 100644
index 0000000..3827c18
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ViewUrlsService.java
@@ -0,0 +1,144 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.api.services;
+
+import com.google.common.base.Optional;
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.security.authorization.AuthorizationException;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.Collections;
+
+
+/**
+ * Service responsible for view resource requests.
+ */
+@Path("/view/urls")
+public class ViewUrlsService extends BaseService {
+
+  /**
+   * Get the list of all registered view URLs
+   * @param headers
+   * @param ui
+
+   * @return collections of all view urls and any instances registered against them
+   */
+  @GET
+  @Produces("text/plain")
+  public Response getViewUrls(@Context HttpHeaders headers, @Context UriInfo ui) {
+    return handleRequest(headers, null, ui, Request.Type.GET, createViewUrlResource(Optional.<String>absent()));
+  }
+
+
+  /**
+   * Create a new View URL
+   * @param body
+   * @param headers
+   * @param ui
+   * @param urlName
+   * @return
+   * @throws AuthorizationException
+     */
+  @POST
+  @Path("{urlName}")
+  @Produces("text/plain")
+  public Response createUrl(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                                @PathParam("urlName") String urlName) throws AuthorizationException {
+    return handleRequest(headers, body, ui, Request.Type.POST, createViewUrlResource(Optional.of(urlName)));
+  }
+
+
+  /**
+   * Update a view URL
+   * @param body
+   * @param headers
+   * @param ui
+   * @param urlName
+   * @return
+   * @throws AuthorizationException
+     */
+  @PUT
+  @Path("{urlName}")
+  @Produces("text/plain")
+  public Response updateUrl(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                            @PathParam("urlName") String urlName) throws AuthorizationException {
+    return handleRequest(headers, body, ui, Request.Type.PUT, createViewUrlResource(Optional.of(urlName)));
+  }
+
+  /**
+   * Remove a view URL
+   * @param body
+   * @param headers
+   * @param ui
+   * @param urlName
+   * @return
+   * @throws AuthorizationException
+     */
+  @DELETE
+  @Path("{urlName}")
+  @Produces("text/plain")
+  public Response deleteUrl(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                            @PathParam("urlName") String urlName) throws AuthorizationException {
+    return handleRequest(headers, body, ui, Request.Type.DELETE, createViewUrlResource(Optional.of(urlName)));
+  }
+
+
+  /**
+   * Get information about a single view URL
+   * @param headers
+   * @param ui
+   * @param urlName
+   * @return
+   * @throws AuthorizationException
+     */
+  @GET
+  @Path("{urlName}")
+  @Produces("text/plain")
+  public Response getUrl(@Context HttpHeaders headers, @Context UriInfo ui,
+                                @PathParam("urlName") String urlName) throws AuthorizationException {
+    return handleRequest(headers, null, ui, Request.Type.GET, createViewUrlResource(Optional.of(urlName)));
+  }
+
+
+
+
+  // ----- helper methods ----------------------------------------------------
+
+  /**
+   * Create a view URL resource.
+   *
+   * @param urlName Name of the URL
+   *
+   * @return a view URL resource instance
+   */
+  private ResourceInstance createViewUrlResource(Optional<String> urlName) {
+    return createResource(Resource.Type.ViewURL,Collections.singletonMap(Resource.Type.ViewURL, urlName.isPresent()?urlName.get().toString():null));
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
index c7dc117..4e7a032 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
@@ -68,6 +68,8 @@ public class DefaultProviderModule extends AbstractProviderModule {
         return new ViewVersionResourceProvider();
       case ViewInstance:
         return new ViewInstanceResourceProvider();
+      case ViewURL:
+        return new ViewURLResourceProvider();
       case StackServiceComponentDependency:
         return new StackDependencyResourceProvider(propertyIds, keyPropertyIds);
       case Permission:

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
index 6523962..2a9eee7 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
@@ -35,6 +35,7 @@ import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.orm.entities.ViewEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceDataEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
+import org.apache.ambari.server.orm.entities.ViewURLEntity;
 import org.apache.ambari.server.security.authorization.RoleAuthorization;
 import org.apache.ambari.server.view.ViewRegistry;
 import org.apache.ambari.server.view.validation.InstanceValidationResultImpl;
@@ -242,7 +243,10 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv
     setResourceProperty(resource, VISIBLE_PROPERTY_ID, viewInstanceEntity.isVisible(), requestedIds);
     setResourceProperty(resource, STATIC_PROPERTY_ID, viewInstanceEntity.isXmlDriven(), requestedIds);
     setResourceProperty(resource, CLUSTER_HANDLE_PROPERTY_ID, viewInstanceEntity.getClusterHandle(), requestedIds);
-    setResourceProperty(resource, SHORT_URL_PROPERTY_ID, viewInstanceEntity.getShortUrl(), requestedIds);
+    ViewURLEntity viewUrl = viewInstanceEntity.getViewUrl();
+    if(viewUrl != null) {
+      setResourceProperty(resource, SHORT_URL_PROPERTY_ID, viewUrl.getUrlSuffix(), requestedIds);
+    }
 
     // only allow an admin to access the view properties
     if (ViewRegistry.getInstance().checkAdmin()) {
@@ -344,11 +348,6 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv
       viewInstanceEntity.setClusterHandle((String) properties.get(CLUSTER_HANDLE_PROPERTY_ID));
     }
 
-    if (properties.containsKey(SHORT_URL_PROPERTY_ID)) {
-      viewInstanceEntity.setShortUrl((String) properties.get(SHORT_URL_PROPERTY_ID));
-    }
-
-
     Map<String, String> instanceProperties = new HashMap<String, String>();
 
     boolean isUserAdmin = viewRegistry.checkAdmin();
@@ -402,11 +401,6 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv
             throw new IllegalStateException("The view " + viewName + " is not loaded.");
           }
 
-          if(!Strings.isNullOrEmpty(instanceEntity.getShortUrl()) && viewRegistry.duplicatedShortUrl(instanceEntity)){
-            throw new DuplicateResourceException("The short url " + instanceEntity.getShortUrl() + " already exists for "+ instanceEntity.getViewEntity().getCommonName() +
-                    " and version "+instanceEntity.getViewEntity().getVersion());
-          }
-
           if (viewRegistry.instanceExists(instanceEntity)) {
             throw new DuplicateResourceException("The instance " + instanceEntity.getName() + " already exists.");
           }
@@ -428,16 +422,9 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv
       @Transactional
       @Override
       public Void invoke() throws AmbariException {
-        ViewRegistry       viewRegistry   = ViewRegistry.getInstance();
-
         ViewInstanceEntity instance = toEntity(properties, true);
         ViewEntity         view     = instance.getViewEntity();
 
-        if(!Strings.isNullOrEmpty(instance.getShortUrl()) && viewRegistry.duplicatedShortUrl(instance)){
-          throw new DuplicateResourceException("The short url " + instance.getShortUrl() + " already exists for "+ instance.getViewEntity().getCommonName() +
-                  " and version "+instance.getViewEntity().getVersion());
-        }
-
         if (includeInstance(view.getCommonName(), view.getVersion(), instance.getInstanceName(), false)) {
           try {
             ViewRegistry.getInstance().updateViewInstance(instance);