All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.structurizr.Workspace Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
package com.structurizr;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.structurizr.documentation.Documentable;
import com.structurizr.documentation.Documentation;
import com.structurizr.model.*;
import com.structurizr.view.ViewSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.annotation.Nonnull;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

/**
 * Represents a Structurizr workspace, which is a wrapper for a
 * software architecture model, views and documentation.
 */
public final class Workspace extends AbstractWorkspace implements Documentable {

    private static final Log log = LogFactory.getLog(Workspace.class);

    private Model model = createModel();
    private ViewSet viewSet;
    private Documentation documentation;

    Workspace() {
    }

    /**
     * Creates a new workspace.
     *
     * @param name          the name of the workspace
     * @param description   a short description
     */
    public Workspace(String name, String description) {
        super(name, description);

        model = createModel();
        viewSet = createViewSet();
        documentation = new Documentation();
    }

    /**
     * Gets the software architecture model.
     *
     * @return  a Model instance
     */
    public Model getModel() {
        return model;
    }

    void setModel(Model model) {
        this.model = model;
    }

    /**
     * Gets the set of views onto a software architecture model.
     *
     * @return  a ViewSet instance
     */
    public ViewSet getViews() {
        return viewSet;
    }

    void setViews(ViewSet viewSet) {
        this.viewSet = viewSet;
    }

    private Model createModel() {
        try {
            Constructor constructor = Model.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            return (Model)constructor.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private ViewSet createViewSet() {
        try {
            Constructor constructor = ViewSet.class.getDeclaredConstructor(Model.class);
            constructor.setAccessible(true);
            return (ViewSet)constructor.newInstance(model);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Called when deserialising JSON, to re-create the object graph
     * based upon element/relationship IDs.
     */
    public void hydrate() {
        if (viewSet == null) {
            viewSet = createViewSet();
        }

        hydrateModel();
        hydrateViewSet();
    }

    private void hydrateModel() {
        try {
            Method hydrateMethod = Model.class.getDeclaredMethod("hydrate");
            hydrateMethod.setAccessible(true);
            hydrateMethod.invoke(model);
        } catch (InvocationTargetException ite) {
            if (ite.getCause() != null && ite.getCause() instanceof WorkspaceValidationException) {
                throw (WorkspaceValidationException)ite.getCause();
            } else {
                throw new RuntimeException(ite.getCause());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void hydrateViewSet() {
        try {
            Method hydrateMethod = ViewSet.class.getDeclaredMethod("hydrate", Model.class);
            hydrateMethod.setAccessible(true);
            hydrateMethod.invoke(viewSet, model);
        } catch (InvocationTargetException ite) {
            if (ite.getCause() != null && ite.getCause() instanceof WorkspaceValidationException) {
                throw (WorkspaceValidationException)ite.getCause();
            } else {
                throw new RuntimeException(ite.getCause());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Gets the documentation associated with this workspace.
     *
     * @return  a Documentation object
     */
    public Documentation getDocumentation() {
        return documentation;
    }

    /**
     * Sets the documentation associated with this workspace.
     *
     * @param documentation a Documentation object
     */
    void setDocumentation(@Nonnull Documentation documentation) {
        this.documentation = documentation;
    }

    /**
     * Determines whether this model is empty.
     *
     * @return  true if the model has no elements, views or documentation; false otherwise
     */
    @JsonIgnore
    public boolean isEmpty() {
        return model.isEmpty() && viewSet.isEmpty() && documentation.isEmpty();
    }

    /**
     * Counts and logs any warnings within the workspace (e.g. missing element descriptions).
     *
     * @return  the number of warnings
     */
    public int countAndLogWarnings() {
        final List warnings = new LinkedList<>();

        // find elements with a missing description
        getModel().getElements().stream()
                .filter(e -> !(e instanceof SoftwareSystemInstance) && !(e instanceof ContainerInstance) && !(e instanceof DeploymentNode) && !(e instanceof InfrastructureNode))
                .filter(e -> e.getDescription() == null || e.getDescription().trim().length() == 0)
                .forEach(e -> warnings.add(e.getCanonicalName() + " is missing a description."));

        // find containers with a missing technology
        getModel().getElements().stream()
                .filter(e -> e instanceof Container)
                .map(e -> (Container)e)
                .filter(c -> c.getTechnology() == null || c.getTechnology().trim().length() == 0)
                .forEach(c -> warnings.add(c.getCanonicalName() + " is missing a technology."));

        // find components with a missing technology
        getModel().getElements().stream()
                .filter(e -> e instanceof Component)
                .map(e -> (Component)e)
                .filter(c -> c.getTechnology() == null || c.getTechnology().trim().length() == 0)
                .forEach(c -> warnings.add(c.getCanonicalName() + " is missing a technology."));

        // find component relationships with a missing description
        for (Relationship relationship : getModel().getRelationships()) {
            if (relationship.getSource() instanceof Component && relationship.getDestination() instanceof Component &&
                    relationship.getSource().getParent().equals(relationship.getDestination().getParent())) {
                // ignore component-component relationships inside the same container because these are
                // often identified using reflection and won't have a description
                // (i.e. let's not flood the user with warnings)
            } else {
                if (relationship.getDescription() == null || relationship.getDescription().trim().length() == 0) {
                    warnings.add("The relationship between " + relationship.getSource().getCanonicalName() + " and " + relationship.getDestination().getCanonicalName() + " is missing a description.");
                }
            }
        }

        // diagram keys have not been specified - this is only applicable to
        // workspaces created with the early versions of Structurizr for Java
        getViews().getSystemLandscapeViews().stream()
                .filter(v -> v.getKey() == null)
                .forEach(v -> warnings.add("System Landscape view \"" + v.getName() + "\": Missing key"));
        getViews().getSystemContextViews().stream()
                .filter(v -> v.getKey() == null)
                .forEach(v -> warnings.add("System Context view \"" + v.getName() + "\": Missing key"));
        getViews().getContainerViews().stream()
                .filter(v -> v.getKey() == null)
                .forEach(v -> warnings.add("Container view \"" + v.getName() + "\": Missing key"));
        getViews().getComponentViews().stream()
                .filter(v -> v.getKey() == null)
                .forEach(v -> warnings.add("Component view \"" + v.getName() + "\": Missing key"));
        getViews().getDynamicViews().stream()
                .filter(v -> v.getKey() == null)
                .forEach(v -> warnings.add("Dynamic view \"" + v.getName() + "\": Missing key"));
        getViews().getDeploymentViews().stream()
                .filter(v -> v.getKey() == null)
                .forEach(v -> warnings.add("Deployment view \"" + v.getName() + "\": Missing key"));

        warnings.forEach(log::warn);

        return warnings.size();
    }

    private String typeof(Element element) {
        if (element instanceof SoftwareSystem) {
            return "software system";
        } else {
            return element.getClass().getSimpleName().toLowerCase();
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy