public class DefaultFileNameBuilder extends AbstractFileNameBuilder {

    protected ResourceMetaData metaData;

    protected When when;

    protected ResourceIdentifier<ResourceType> resourceIdentifier;

    public DefaultFileNameBuilder() {
        setDefaultFileIdentifier();
    }

    public DefaultFileNameBuilder withMetaData(ResourceMetaData metaData) {
        this.metaData = metaData;
        return this;
    }

    public DefaultFileNameBuilder withStage(When when) {
        this.when = when;
        return this;
    }

    public DefaultFileNameBuilder withResourceIdentifier(ResourceIdentifier<ResourceType> resourceIdentifier) {
        if (resourceIdentifier != null) {
            this.resourceIdentifier = resourceIdentifier;
        }
        return this;
    }

    /**
     * When generation of file name is not overridden by {@link #withResourceIdentifier(ResourceIdentifier)}, by default, every
     * file name will be of format: <br>
     * <br>
     * testMethodName_stage.screenshotType <br>
     * <br>
     * where stage is {@link When}.
     *
     * When you do not set meta data or name of test method is null or empty string, random {@link UUID} is generated instead of
     * that. If {@link When} is not set, it is not included into file name generation. When you do not specify resource type,
     * file name will be generated without file format suffix, excluding a dot as well.
     *
     * <br>
     * After you get identifier, builder is cleared so every subsequent call of this method will return just UUID when there are
     * not used 'when' methods.
     */
    @Override
    public String build() {
        ResourceType resourceType = null;
        if (metaData != null) {
            resourceType = metaData.getResourceType();
        }
        String id = resourceIdentifier.getIdentifier(resourceType);
        clear();
        return id;
    }

    @Override
    public DefaultFileNameBuilder clear() {
        metaData = null;
        when = null;
        return this;
    }

    private void setDefaultFileIdentifier() {
        resourceIdentifier = new ResourceIdentifier<ResourceType>() {

            @Override
            public String getIdentifier(ResourceType resourceType) {
                StringBuilder sb = new StringBuilder();
                if (metaData == null || metaData.getTestMethodName() == null || metaData.getTestMethodName().isEmpty()) {
                    sb.append(UUID.randomUUID().toString());
                } else {
                    sb.append(metaData.getTestMethodName());
                }
                if (when != null) {
                    sb.append("_");
                    sb.append(when.toString());
                }
                if (resourceType != null) {
                    sb.append(".");
                    sb.append(resourceType.toString());
                }
                return sb.toString();
            }
        };
    }
} public class VideoConfiguration extends Configuration<VideoConfiguration> {

    private static final String ROOT_DIR = "target/videos";

    private static final String VIDEO_TYPE = VideoType.MP4.name();

    private static final String START_BEFORE_TEST = "false";

    private static final String START_BEFORE_CLASS = "false";

    private static final String START_BEFORE_SUITE = "false";

    private static final String TAKE_ONLY_ON_FAIL = "true";

    private static final String VIDEO_NAME = "record";

    private static final String TEST_TIMEOUT = "1800"; // 30 minutes

    private static final String WIDTH = "0"; // pixels

    private static final String HEIGHT = "0"; // pixels.

    private final ReporterConfiguration reporterConfiguration;

    public VideoConfiguration(ReporterConfiguration reporterConfiguration) {
        this.reporterConfiguration = reporterConfiguration;
    }

    /**
     * By default set to true
     *
     * @return true if video should be taken only for the failed tests
     */
    public boolean getTakeOnlyOnFail() {
        return Boolean.parseBoolean(getProperty("takeOnlyOnFail", TAKE_ONLY_ON_FAIL));
    }

    /**
     * By default set to 1800, i.e. 30 minutes
     *
     * @return timeout for each test in order to stop recording and save the video file if the test gets stuck
     */
    public int getTestTimeout() {
        return Integer.parseInt(getProperty("testTimeout", TEST_TIMEOUT));
    }

    /**
     * By default set to "record"
     *
     * @return video name which will be used in case you want to record whole test suite into one file.
     */
    public String getVideoName() {
        return getProperty("videoName", VIDEO_NAME);
    }

    /**
     * By default set to "target/videos"
     *
     * @return root folder where all videos will be placed. Directory structure is left on the extension itself.
     */
    public File getRootDir() {
        return new File(getProperty("rootDir", ROOT_DIR));
    }

    /**
     * By default set to "MP4".
     *
     * @return type of video we want our screenshots to be of
     */
    public String getVideoType() {
        return getProperty("videoType", VIDEO_TYPE).toUpperCase();
    }

    /**
     * By default set to false.
     *
     * @return true if video recording should start before test, false otherwise
     */
    public boolean getStartBeforeTest() {
        return Boolean.parseBoolean(getProperty("startBeforeTest", START_BEFORE_TEST));
    }

    /**
     * By default set to false.
     *
     * @return true if video should be taken before class, false otherwise
     */
    public boolean getStartBeforeClass() {
        return Boolean.parseBoolean(getProperty("startBeforeClass", START_BEFORE_CLASS));
    }

    /**
     * By default set to false.
     *
     * @return true if screenshot should be taken before suite, false otherwise
     */
    public boolean getStartBeforeSuite() {
        return Boolean.parseBoolean(getProperty("startBeforeSuite", START_BEFORE_SUITE));
    }

    /**
     * By default set to 0, you have to set this in configuration in order to use it.
     *
     * @return height of recorded video
     */
    public int getHeight() {
        return Integer.parseInt(getProperty("height", HEIGHT));
    }

    /**
     * By default set to 0, you have to set this in configuration in order to use it.
     *
     * @return width of recorder video
     */
    public int getWidth() {
        return Integer.parseInt(getProperty("width", WIDTH));
    }

    @Override
    public void validate() throws VideoConfigurationException {
        validate(reporterConfiguration);
    }

    private void validate(ReporterConfiguration reporterConfiguration) {
        try {
            VideoType.valueOf(VideoType.class, getVideoType());
        } catch (IllegalArgumentException ex) {
            throw new VideoConfigurationException(
                "Video type you specified in arquillian.xml is not valid video type. "
                        + "The configured video type is: " + getVideoType() + ". "
                        + "The supported video types are: " + VideoType.getAll());
        }

        final String report = reporterConfiguration.getReport().toLowerCase();

        if (report.contains("htm") || report.contains("ad") || report.equals("asciidoc")) {
            final File recorderRootDir = new File(reporterConfiguration.getRootDir(), "videos");
            if (!getRootDir().equals(recorderRootDir)) {
                setProperty("rootDir", recorderRootDir.getAbsolutePath());
            }
        }

        try {
            if (!getRootDir().exists()) {
                boolean created = getRootDir().mkdirs();
                if (!created) {
                    throw new VideoConfigurationException("Unable to create root directory " + getRootDir().getAbsolutePath());
                }
            } else {
                if (!getRootDir().isDirectory()) {
                    throw new VideoConfigurationException("Root directory you specified is not a directory - " +
                        getRootDir().getAbsolutePath());
                }
                if (!getRootDir().canWrite()) {
                    throw new VideoConfigurationException(
                        "You can not write to '" + getRootDir().getAbsolutePath() + "'.");
                }
            }
        } catch (SecurityException ex) {
            throw new VideoConfigurationException(
                "You are not permitted to operate on specified resource: " + getRootDir().getAbsolutePath() + "'.", ex);
        }

        try {
            if (Integer.parseInt(getProperty("width", WIDTH)) < 0) {
                throw new VideoConfigurationException("It seems you have set width of video to be lower then 0.");
            }
        } catch (NumberFormatException ex) {
            throw new VideoConfigurationException("Provided width of video is not recognized to be an integer number.");
        }

        try {
            if (Integer.parseInt(getProperty("height", HEIGHT)) < 0) {
                throw new VideoConfigurationException("It seems you have set height of video to be lower then 0.");
            }
        } catch (NumberFormatException ex) {
            throw new VideoConfigurationException("Provided height of video is not recognized to be an integer number.");
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("%-40s %s%n", "startBeforeSuite", getStartBeforeSuite()));
        sb.append(String.format("%-40s %s%n", "startBeforeClass", getStartBeforeClass()));
        sb.append(String.format("%-40s %s%n", "startBeforeTest", getStartBeforeTest()));
        sb.append(String.format("%-40s %s%n", "takeOnlyOnFail", getTakeOnlyOnFail()));
        sb.append(String.format("%-40s %s%n", "testTimeOut", getTestTimeout()));
        sb.append(String.format("%-40s %s%n", "rootDir", getRootDir()));
        sb.append(String.format("%-40s %s%n", "videoName", getVideoName()));
        sb.append(String.format("%-40s %s%n", "videoType", getVideoType()));
        sb.append(String.format("%-40s %s%n", "width", getWidth()));
        sb.append(String.format("%-40s %s%n", "height", getHeight()));
        return sb.toString();
    }

} public class StartRecordClassVideo {

    private final VideoMetaData videoMetaData;
    private final VideoType videoType;

    public StartRecordClassVideo(VideoType videoType, VideoMetaData videoMetaData) {
        this.videoMetaData = videoMetaData;
        this.videoType = videoType;
    }

    public VideoMetaData getVideoMetaData() {
        return videoMetaData;
    }

    public VideoType getVideoType() {
        return videoType;
    }
} public class StartRecordSuiteVideo {

    private final VideoType videoType;
    private final VideoMetaData videoMetaData;

    public StartRecordSuiteVideo(VideoType videoType, VideoMetaData videoMetaData) {
        this.videoType = videoType;
        this.videoMetaData = videoMetaData;
    }

    public VideoType getVideoType() {
        return videoType;
    }

    public VideoMetaData getVideoMetaData() {
        return videoMetaData;
    }

} public class StartRecordVideo {

    private final VideoType videoType;
    private final VideoMetaData videoMetaData;

    public StartRecordVideo(VideoType videoType, VideoMetaData videoMetaData) {
        this.videoType = videoType;
        this.videoMetaData = videoMetaData;
    }

    public VideoType getVideoType() {
        return videoType;
    }

    public VideoMetaData getVideoMetaData() {
        return videoMetaData;
    }

} public class StopRecordClassVideo {
    private final VideoType videoType;
    private final VideoMetaData videoMetaData;

    public StopRecordClassVideo(VideoType videoType, VideoMetaData videoMetaData) {
        this.videoMetaData = videoMetaData;
        this.videoType = videoType;
    }

    public VideoMetaData getVideoMetaData() {
        return videoMetaData;
    }

    public VideoType getVideoType() {
        return videoType;
    }
} public class StopRecordSuiteVideo {

    private final VideoType videoType;
    private final VideoMetaData videoMetaData;

    public StopRecordSuiteVideo(VideoType videoType, VideoMetaData videoMetaData) {
        this.videoMetaData = videoMetaData;
        this.videoType = videoType;
    }

    public VideoMetaData getVideoMetaData() {
        return videoMetaData;
    }

    public VideoType getVideoType() {
        return videoType;
    }

} public class StopRecordVideo {

    private final VideoMetaData videoMetaData;
    private final VideoType videoType;

    public StopRecordVideo(VideoType videoType, VideoMetaData videoMetaData) {
        this.videoMetaData = videoMetaData;
        this.videoType = videoType;
    }

    public VideoMetaData getVideoMetaData() {
        return videoMetaData;
    }

    public VideoType getVideoType() {
        return videoType;
    }
} public abstract class PropertyEntry implements ReportEntry {

    @XmlTransient
    private final List<PropertyEntry> propertyEntries = new ArrayList<PropertyEntry>();

    @Override
    public List<PropertyEntry> getPropertyEntries() {
        return propertyEntries;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        PropertyEntry that = (PropertyEntry) o;

        return propertyEntries.equals(that.propertyEntries);

    }

    @Override
    public int hashCode() {
        return propertyEntries.hashCode();
    }
} public class PropertyReportEvent {

    private final PropertyEntry propertyEntry;

    /**
     *
     * @param propertyEntry property entry to hook into reporting tree from your extension
     * @throws IllegalArgumentException if {@code propertyEntry} is a null object
     */
    public PropertyReportEvent(PropertyEntry propertyEntry) {
        Validate.notNull(propertyEntry, "property entry can not be a null object");
        this.propertyEntry = propertyEntry;
    }

    public PropertyEntry getPropertyEntry() {
        return propertyEntry;
    }
} @XmlRootElement(name = "video")
@XmlType(propOrder = { "phase", "width", "height", "link" })
public class VideoEntry extends FileEntry {

    private When phase;

    private int width;

    private int height;

    private String link;

    @XmlAttribute(required = false)
    public When getPhase() {
        return phase;
    }

    public void setPhase(When phase) {
        this.phase = phase;
    }

    @XmlAttribute(required = false)
    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    @XmlAttribute(required = false)
    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @XmlAttribute
    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;

        VideoEntry that = (VideoEntry) o;

        if (width != that.width) return false;
        if (height != that.height) return false;
        if (phase != that.phase) return false;
        return link != null ? link.equals(that.link) : that.link == null;

    }

    @Override
    public int hashCode() {
        int result = super.hashCode();
        result = 31 * result + (phase != null ? phase.hashCode() : 0);
        result = 31 * result + width;
        result = 31 * result + height;
        result = 31 * result + (link != null ? link.hashCode() : 0);
        return result;
    }
}
import java.io.File;
import java.io.IOException;
import org.arquillian.extension.recorder.DefaultFileNameBuilder;
import org.arquillian.extension.recorder.video.Recorder;
import org.arquillian.extension.recorder.video.Video;
import org.arquillian.extension.recorder.video.VideoConfiguration;
import org.arquillian.extension.recorder.video.VideoMetaData;
import org.arquillian.extension.recorder.video.event.StartRecordClassVideo;
import org.arquillian.extension.recorder.video.event.StartRecordSuiteVideo;
import org.arquillian.extension.recorder.video.event.StartRecordVideo;
import org.arquillian.extension.recorder.video.event.StopRecordClassVideo;
import org.arquillian.extension.recorder.video.event.StopRecordSuiteVideo;
import org.arquillian.extension.recorder.video.event.StopRecordVideo;
import org.arquillian.recorder.reporter.PropertyEntry;
import org.arquillian.recorder.reporter.event.PropertyReportEvent;
import org.arquillian.recorder.reporter.impl.TakenResourceRegister;
import org.arquillian.recorder.reporter.model.entry.VideoEntry;
import org.jboss.arquillian.core.api.Event;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.annotation.Inject;
import org.jboss.arquillian.core.api.annotation.Observes;
import org.jboss.arquillian.test.spi.TestResult;
import org.jboss.arquillian.test.spi.TestResult.Status;
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
 * contributors by the @authors tag. See the copyright.txt in the
 * distribution for a full listing of individual contributors.
 *
 * 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.
 */
package org.arquillian.extension.recorder.video.impl;



/**
 * @author <a href="smikloso@redhat.com">Stefan Miklosovic</a>
 *
 */
public class VideoTaker {

    @Inject
    private Instance<Recorder> recorder;

    @Inject
    private Instance<VideoConfiguration> configuration;

    @Inject
    private Event<PropertyReportEvent> propertyReportEvent;

    @Inject
    private Instance<TakenResourceRegister> resourceRegister;

    private final DefaultFileNameBuilder nb = new DefaultFileNameBuilder();

    // starts

    public void onStartSuiteRecording(@Observes StartRecordSuiteVideo event) {
        File videoTarget = new File("suite", configuration.get().getVideoName());
        recorder.get().startRecording(videoTarget, event.getVideoType());
    }

    public void onStartClassRecording(@Observes StartRecordClassVideo event) {
        File videoTarget = new File("class", configuration.get().getVideoName());
        recorder.get().startRecording(videoTarget, event.getVideoType());
    }

    public void onStartRecording(@Observes StartRecordVideo event) {

        VideoMetaData metaData = event.getVideoMetaData();
        metaData.setResourceType(event.getVideoType());
        String fileName = nb
            .withMetaData(metaData)
            .build();

        File videoTarget = new File(event.getVideoMetaData().getTestClassName(), fileName);
        recorder.get().startRecording(videoTarget, event.getVideoType());
    }

    // stops

    public void onStopSuiteRecording(@Observes StopRecordSuiteVideo event) {
        Video video = recorder.get().stopRecording();
        resourceRegister.get().addTaken(video);
        video.setResourceMetaData(event.getVideoMetaData());

        resourceRegister.get().addReported(video);
        propertyReportEvent.fire(new PropertyReportEvent(getVideoEntry(video)));
    }

    public void onStopClassRecording(@Observes StopRecordClassVideo event) {
        Video video = recorder.get().stopRecording();
        video.setResourceMetaData(event.getVideoMetaData());
        resourceRegister.get().addReported(video);

        propertyReportEvent.fire(new PropertyReportEvent(getVideoEntry(video)));
    }

    public void onStopRecording(@Observes StopRecordVideo event) throws IOException {
        Video video = recorder.get().stopRecording();

        TestResult testResult = event.getVideoMetaData().getTestResult();

        if (testResult != null) {
            Status status = testResult.getStatus();
            appendStatus(video, status);

            resourceRegister.get().addReported(video);

            if ((status.equals(Status.PASSED) && !configuration.get().getTakeOnlyOnFail())
                || (status.equals(Status.FAILED) && configuration.get().getTakeOnlyOnFail())) {
                propertyReportEvent.fire(new PropertyReportEvent(getVideoEntry(video)));
            }
        } else {
            resourceRegister.get().addTaken(video);
        }
    }

    private PropertyEntry getVideoEntry(Video video) {
        VideoEntry videoEntry = new VideoEntry();