package com.sixsq.slipstream.persistence; /* * +=================================================================+ * SlipStream Server (WAR) * ===== * Copyright (C) 2013 SixSq Sarl (sixsq.com) * ===== * Licensed 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. * -=================================================================- */ import java.util.List; import java.util.Map; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.EntityManager; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.NoResultException; import javax.persistence.OneToOne; import javax.persistence.Query; import javax.persistence.Transient; import org.simpleframework.xml.Attribute; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementArray; import org.simpleframework.xml.ElementMap; import com.sixsq.slipstream.exceptions.ValidationException; import com.sixsq.slipstream.module.ModuleVersionView; import com.sixsq.slipstream.module.ModuleView; import com.sixsq.slipstream.run.RunViewList; import com.sixsq.slipstream.util.ModuleUriUtil; /** * Unit test see: * * @see ModuleTest * */ @SuppressWarnings("serial") @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @NamedQueries({ @NamedQuery(name = "moduleLastVersion", query = "SELECT m FROM Module m WHERE m.version = (SELECT MAX(n.version) FROM Module n WHERE n.name = :name AND n.deleted != TRUE)"), @NamedQuery(name = "moduleViewLatestChildren", query = "SELECT NEW com.sixsq.slipstream.module.ModuleView(m.resourceUri, m.description, m.category, m.customVersion, m.authz) FROM Module m WHERE m.parentUri = :parent AND m.version = (SELECT MAX(c.version) FROM Module c WHERE c.name = m.name AND c.deleted != TRUE)"), @NamedQuery(name = "moduleViewAllVersions", query = "SELECT NEW com.sixsq.slipstream.module.ModuleVersionView(m.resourceUri, m.version, m.lastModified, m.commit, m.authz, m.category, m.name) FROM Module m WHERE m.name = :name AND m.deleted != TRUE"), @NamedQuery(name = "moduleAll", query = "SELECT m FROM Module m WHERE m.deleted != TRUE"), @NamedQuery(name = "moduleViewPublished", query = "SELECT NEW com.sixsq.slipstream.module.ModuleViewPublished(m.resourceUri, m.description, m.category, m.customVersion, m.authz, m.logoLink) FROM Module m WHERE m.published != null AND m.deleted != TRUE") }) public abstract class Module extends Parameterized<Module, ModuleParameter> implements Guarded { public final static String RESOURCE_URI_PREFIX = "module/"; public final static int DEFAULT_VERSION = -1; private static void bindModuleToAuthz(Module module) { if (module != null) { // set authz to bind them module.getAuthz().setGuarded(module); } } private static Module loadByUri(String uri) { EntityManager em = PersistenceUtil.createEntityManager(); Module m = em.find(Module.class, uri); Module latestVersion = loadLatest(uri); em.close(); if (latestVersion != null && m != null) { m.setIsLatestVersion(latestVersion.version); } bindModuleToAuthz(m); return m; } public static Module loadLatest(String resourceUri) { EntityManager em = PersistenceUtil.createEntityManager(); Query q = em.createNamedQuery("moduleLastVersion"); String name = ModuleUriUtil.extractModuleNameFromResourceUri(resourceUri); q.setParameter("name", name); Module module; try { module = (Module) q.getSingleResult(); module.setIsLatestVersion(module.version); } catch (NoResultException ex) { module = null; } em.close(); bindModuleToAuthz(module); return module; } public static boolean exists(String resourceUri) { boolean exists; if (load(resourceUri) != null) { exists = true; } else { exists = false; } return exists; } public static Module loadByName(String name) { return load(constructResourceUri(name)); } public static Module load(String uri) { String resourceUri = uri; int version = ModuleUriUtil.extractVersionFromResourceUri(resourceUri); Module module = (version == DEFAULT_VERSION ? loadLatest(resourceUri) : loadByUri(resourceUri)); return module; } @SuppressWarnings("unchecked") public static List<Module> listAll() { EntityManager em = PersistenceUtil.createEntityManager(); Query q = em.createNamedQuery("moduleAll"); List<Module> list = q.getResultList(); em.close(); return list; } @SuppressWarnings("unchecked") public static boolean isEmpty() { EntityManager em = PersistenceUtil.createEntityManager(); Query q = em.createNamedQuery("moduleAll"); q.setMaxResults(1); List<Module> list = q.getResultList(); em.close(); return list.size() == 0; } @SuppressWarnings("unchecked") public static List<ModuleView> viewList(String resourceUri) { EntityManager em = PersistenceUtil.createEntityManager(); Query q = em.createNamedQuery("moduleViewLatestChildren"); q.setParameter("parent", Module.constructResourceUri(ModuleUriUtil.extractModuleNameFromResourceUri(resourceUri))); List<ModuleView> list = q.getResultList(); em.close(); return list; } @SuppressWarnings("unchecked") public static List<ModuleVersionView> viewListAllVersions(String resourceUri) { EntityManager em = PersistenceUtil.createEntityManager(); Query q = em.createNamedQuery("moduleViewAllVersions"); String name = ModuleUriUtil.extractModuleNameFromResourceUri(resourceUri); q.setParameter("name", name); List<ModuleVersionView> list = q.getResultList(); em.close(); return list; } @SuppressWarnings("unchecked") public static List<ModuleView> viewPublishedList() { EntityManager em = PersistenceUtil.createEntityManager(); Query q = em.createNamedQuery("moduleViewPublished"); List<ModuleView> list = q.getResultList(); em.close(); return list; } public static String constructResourceUri(String name) { return RESOURCE_URI_PREFIX + name; } public static String constructResourceUrl(String name, int version) { return constructResourceUri(name + "/" + String.valueOf(version)); } @Attribute @Id private String resourceUri; @Element(required = false) @Embedded private Authz authz = new Authz("", this); @Transient private Module parent = null; @Attribute(required = false) private String customVersion; /** * Module hierarchy (e.g. <parent>/<module>) */ @Attribute(required = true) private String parentUri; /** * Last part of the module hierarchy (e.g. <parent>/<module>) */ @Attribute(required = true) private String name; @Attribute(required = true) private int version; /** * Intended to inform users about constraints or requirements on the * module requirements - e.g. required ports. */ @Element(required = false, data = true) @Column(length = 65536) private String note; @Transient @Attribute(required = false) private boolean isLatestVersion; @Attribute(required = false) @Column(length = 1024) private String tag; @Element(required = false) @OneToOne(optional = true, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private Commit commit; @Element(required = false) @OneToOne(optional = true, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private Publish published; // to the app store /** * Module reference is a URL. * <p/> * In the case "Image", the module to use as a base image from which to * extract the cloud image id (e.g. AMI). It can be empty if the ImageId * field is provided. */ @Attribute(required = false) private String moduleReferenceUri; /** * Contains all available cloud services. Used for HTML UI. */ @Transient @ElementArray(required = false) protected String[] cloudNames; @Transient @Element(required = false) private RunViewList runs; @Attribute(required = false) private String logoLink; @Attribute(required = false) private String placementPolicy; protected Module() { super(); } @Override @ElementMap(name = "parameters", required = false, valueType = ModuleParameter.class) protected void setParameters(Map<String, ModuleParameter> parameters) { this.parameters = parameters; } @Override @ElementMap(name = "parameters", required = false, valueType = ModuleParameter.class) public Map<String, ModuleParameter> getParameters() { return parameters; } public Module(String name, ModuleCategory category) throws ValidationException { this.category = category; setName(name); } public Guarded getGuardedParent() { if (parent == null) { if (parentUri != null) { parent = Module.load(parentUri); } } return parent; } public void clearGuardedParent() { parent = null; } public RunViewList getRuns() { return runs; } public void setRuns(RunViewList runs) { this.runs = runs; } private void validateName(String name) throws ValidationException { if (name == null || "".equals(name)) { throw new ValidationException("module name cannot be empty"); } if (ModuleUriUtil.extractVersionFromResourceUri(name) != -1) { throw new ValidationException("Invalid name, cannot be an integer"); } if (name.contains(" ")) { throw new ValidationException("Invalid name, cannot contain space"); } } @Attribute public String getShortName() { return ModuleUriUtil.extractShortNameFromResourceUri(resourceUri); } @Attribute public void setShortName(String name) { } public String getOwner() { return authz.getUser(); } @Override public String getResourceUri() { return resourceUri; } public Authz getAuthz() { return authz; } public void setAuthz(Authz authz) { this.authz = authz; } public int getVersion() { return version; } @Override public String getParent() { return parentUri; } @Override public String getName() { return name; } @Override public void setName(String name) throws ValidationException { validateName(name); resourceUri = constructResourceUri(name); extractUriComponents(); } public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public String getCustomVersion() { return customVersion; } public void setCustomVersion(String customVersion) { this.customVersion = customVersion; } public String getModuleReference() { return moduleReferenceUri; } public void setModuleReference(Module reference) throws ValidationException { setModuleReference(reference.getResourceUri()); } public void setModuleReference(String moduleReferenceUri) throws ValidationException { if (moduleReferenceUri != null){ String moduleReferenceUriVersionLess = ModuleUriUtil.extractVersionLessResourceUri(moduleReferenceUri); String moduleUriVersionLess = ModuleUriUtil.extractVersionLessResourceUri(getResourceUri()); if (moduleUriVersionLess.equals(moduleReferenceUriVersionLess)) { throw new ValidationException("Module reference cannot be itself"); } } this.moduleReferenceUri = moduleReferenceUri; } /** * A virtual module is a module that doesn't require to be explicitly built, * since it only defines runtime behavior * * @return true if the module is virtual, false if not */ public boolean isVirtual() { return false; } private void extractUriComponents() throws ValidationException { version = ModuleUriUtil.extractVersionFromResourceUri(resourceUri); parentUri = ModuleUriUtil.extractParentUriFromResourceUri(resourceUri); name = ModuleUriUtil.extractModuleNameFromResourceUri(resourceUri); } @Override public void setContainer(ModuleParameter parameter) { parameter.setContainer(this); } public Module store() { return store(true); } public Module store(boolean incrementVersion) { setLastModified(); if (incrementVersion) { setVersion(); } return (Module) super.store(); } protected void setVersion() { version = VersionCounter.getNextVersion(); resourceUri = Module.constructResourceUri(ModuleUriUtil.extractModuleNameFromResourceUri(resourceUri) + "/" + version); } protected void setIsLatestVersion(int lastVersion) { this.isLatestVersion = version == lastVersion; } public void setCommit(String author, String commit) { this.commit = new Commit(author, commit, this); } public void setCommit(Commit commit) { this.commit = commit; } public Commit getCommit() { return commit; } public String[] getCloudNames() { return cloudNames; } public void setCloudNames(String[] cloudNames) { this.cloudNames = cloudNames; } public String getLogoLink() { return logoLink; } public void setLogoLink(String logoLink) { this.logoLink = logoLink; } public String getPlacementPolicy() { return placementPolicy; } public void setPlacementPolicy(String placementPolicy) { this.placementPolicy= placementPolicy; } public Map<String, String> placementPoliciesPerComponent() { throw new RuntimeException("Invalid call. Accessible only in ImageModule and DeploymentModule classes."); } public String getNote() { return note; } public void setNote(String note) { this.note = note; } public void publish() { published = new Publish(this); } public void unpublish() { published = null; } public Publish getPublished() { return published; } protected String computeParameterValue(String key) throws ValidationException { ModuleParameter parameter = getParameter(key); String value = (parameter == null ? null : parameter.getValue()); if (value == null) { String reference = getModuleReference(); if (reference != null) { Module parent = Module.load(getModuleReference()); if (parent != null) { value = parent.computeParameterValue(key); } } } return value; } public abstract Module copy() throws ValidationException; protected Module copyTo(Module copy) throws ValidationException { copy = (Module) super.copyTo(copy); if (getCommit() != null) { copy.setCommit(getCommit().copy()); } copy.setCustomVersion(getCustomVersion()); copy.setCloudNames((cloudNames == null ? null : cloudNames.clone())); copy.setModuleReference(getModuleReference()); copy.setTag(getTag()); copy.setLogoLink(getLogoLink()); copy.setPlacementPolicy(getPlacementPolicy()); copy.setAuthz(getAuthz().copy(copy)); return copy; } }