public interface SensorInstanceFactory {

    public SensorPlugin createSensorPlugin(SensorTaskService sensorTaskService, SensorConfiguration sensorConfiguration) throws InternalServiceException;
    
    /**
     * @return the type of the sensor plugin to be loaded by this factory.
     */
    public String getPluginType();

}
 public abstract class SensorPlugin implements ResultAccessAvailabilityDescriptor, ResultAccessServiceReference {

    protected SensorConfiguration configuration;

    protected SensorTaskService sensorTaskService;
    
    protected SensorPlugin() {
        // allow default constructor for decoration
    }

    public SensorPlugin(SensorTaskService sensorTaskService, SensorConfiguration configuration) throws InternalServiceException {
        this.configuration = configuration;
        this.sensorTaskService = sensorTaskService;
        sensorTaskService.setProcedure(this);
    }

    public final String getProcedure() {
        return configuration.getProcedure();
    }

    public SensorTaskService getSensorTaskService() {
        return sensorTaskService;
    }

    public void setSensorTaskService(SensorTaskService sensorTaskService) {
        this.sensorTaskService = sensorTaskService;
    }

    /**
     * Delegate submit tasking request to sensor instance for execution. The execution can fail for many
     * reasons so implementors may consider exception handling by adding each occuring exception to the passed
     * {@link OwsExceptionReport} which will be thrown by the SPS framework afterwards.<br>
     * <br>
     * A {@link SensorTask} instance shall be created by the plugin's {@link SensorTaskService} which takes
     * care of storing the task instance into the SPS's {@link SensorTaskRepository}. Once a sensor task is
     * created, the task service can be used to continously update the task within the underlying
     * {@link SensorTaskRepository}.
     * 
     * @param submit
     *        the tasking request
     * @param owsExceptionReport
     *        a container collecting {@link OwsException}s to be thrown together as OWS Exception Report
     *        (according to chapter 8 of [OGC 06-121r3]).
     * @return a SensorTask instance as a result of the Submit task
     * @throws OwsException
     *         if an OwsException is thrown without execption reporting intent.
     */
    public abstract SensorTask submit(SubmitTaskingRequest submit, OwsExceptionReport owsExceptionReport) throws OwsException;

    /*
     * Let the SPS manage this kinds of functionality! Extend SensorPlugin interface only with those 
     * kind of methods a SensorPlugin implementation can provide only!
     * 
     * TODO add abstract getUpdateDescription mehthod
     * TODO add abstract cancel mehod
     * TODO add abstract feasibility method
     * TODO add abstract reservation methods
     * TODO add absrtact update methods
     */

    /**
     * Qualifies sensor's tasking characteristics accordingly. <br>
     * <br>
     * The SPS has to ask a {@link SensorPlugin} instance for a full qualified data component describing its
     * tasking parameters (e.g. AbstractDataComponentType must be qualified to a concrete type implementation
     * like DataRecordType). Implementors can make use of
     * {@link XMLBeansTools#qualifySubstitutionGroup(org.apache.xmlbeans.XmlObject, javax.xml.namespace.QName)}
     * <br>
     * <b>Example:</b>
     * 
     * <pre>
     * {@code
     * public void getQualifiedDataComponent(AbstractDataComponentType componentToQualify) {
     *  QName qname = new QName("http://www.opengis.net/swe/2.0", "DataRecord");
     *  XMLBeansTools.qualifySubstitutionGroup(componentToQualify, qname);
     * }
     * </pre>
     * 
     * @param the
     *        abstract data component to qualify
     */
    public abstract void qualifyDataComponent(AbstractDataComponentType componentToQualify);
    
    public abstract Availability getResultAccessibilityFor(SensorTask sensorTask);

    public String getSensorPluginType() {
        return configuration.getSensorPluginType();
    }

    public SensorConfiguration getSensorConfiguration() {
        return configuration;
    }

    public ResultAccessDataServiceReference getResultAccessDataServiceReference() {
        return configuration.getResultAccessDataService();
    }

    public SensorConfiguration mergeSensorConfigurations(SensorConfiguration sensorConfiguration) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Not yet implemented.");
    }

    @Override
    public final int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ( (getProcedure() == null) ? 0 : getProcedure().hashCode());
        return result;
    }

    @Override
    public final boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SensorPlugin other = (SensorPlugin) obj;
        if (getProcedure() == null) {
            if (other.getProcedure() != null)
                return false;
        }
        else if ( !getProcedure().equals(other.getProcedure()))
            return false;
        return true;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("SensorPluginType: ").append(configuration.getSensorPluginType()).append(", ");
        sb.append("Procedure: ").append(configuration.getProcedure());
        return sb.toString();
    }

}
 public class SensorTaskService {

    private SensorPlugin procedure;

    private SensorTaskRepository sensorTaskRepository;

    public SensorTaskService(SensorTaskRepository sensorTaskRepository) {
        this.sensorTaskRepository = sensorTaskRepository;
    }

    public void setProcedure(SensorPlugin procedure) {
        this.procedure = procedure;
    }

    public String getProcedure() {
        return procedure.getProcedure();
    }

    public SensorTask createNewTask() {
        return sensorTaskRepository.createNewSensorTask(getProcedure());
    }
    
    public void updateSensorTask(SensorTask sensorTask) {
        sensorTaskRepository.saveOrUpdateSensorTask(sensorTask);
    }

    public boolean contains(String taskId) {
        return sensorTaskRepository.containsSensorTask(getProcedure(), taskId);
    }

    public Iterable<SensorTask> getSensorTasks() {
        return sensorTaskRepository.getSensorTasks(getProcedure());
    }

    public SensorTask getSensorTask(String taskId) throws InvalidParameterValueException {
        return sensorTaskRepository.getSensorTask(getProcedure(), taskId);
    }

    public long getAmountOfAvailableTasks() {
        return sensorTaskRepository.getTaskAmountOf(getProcedure());
    }

}
 public class ResultAccessDataServiceReference {
    
    private Long id; // database id
    
    private String reference;
    
    private String role;
    
    private String title;
    
    private String identifier;
    
    private String referenceAbstract;
    
    private String format;

    /**
     * Content of SPSMetadata types
     */
    private List<String> dataAccessTypes;

    public ResultAccessDataServiceReference() {
        // database serialization
    }
    
    Long getId() {
        return id;
    }

    void setId(Long id) {
        this.id = id;
    }

    public String getReference() {
        return reference;
    }

    public void setReference(String reference) {
        this.reference = reference;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getIdentifier() {
        return identifier;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }

    public String getReferenceAbstract() {
        return referenceAbstract;
    }

    public void setReferenceAbstract(String referenceAbstract) {
        this.referenceAbstract = referenceAbstract;
    }

    public String getFormat() {
        return format;
    }

    public void setFormat(String format) {
        this.format = format;
    }

    public List<String> getDataAccessTypes() {
        return dataAccessTypes;
    }

    public void setDataAccessTypes(List<String> dataAccessTypes) {
        this.dataAccessTypes = dataAccessTypes;
    }
    
    
}
 public class SensorConfiguration {

    private Long id; // database id

    private List<SensorOfferingType> sensorOfferings = new ArrayList<SensorOfferingType>();

    private List<SensorDescription> sensorDescriptions = new ArrayList<SensorDescription>();

    private String procedure;

    private String sensorPluginType;
    
    private XmlObject sensorSetup;

    private AbstractDataComponentType taskingParametersTemplate;

    private ResultAccessDataServiceReference resultAccessDataServiceTemplate;

    public SensorConfiguration() {
        // db serialization
    }

    Long getId() {
        return id;
    }

    void setId(Long id) {
        this.id = id;
    }
    
    public List<String> getSensorOfferingsAsString() {
        List<String> offerings = new ArrayList<String>();
        for (SensorOfferingType offeringType : sensorOfferings) {
            offerings.add(offeringType.xmlText());
        }
        return offerings;
    }

    public List<SensorOfferingType> getSensorOfferings() {
        return sensorOfferings;
    }
    
    public void setSensorOfferingsAsString(List<String> sensorOfferings) throws XmlException {
        this.sensorOfferings.clear();
        for (String sensorOffering : sensorOfferings) {
            this.sensorOfferings.add(SensorOfferingType.Factory.parse(sensorOffering));
        }
    }

    public void setSensorOfferings(List<SensorOfferingType> sensorOfferings) {
        this.sensorOfferings = sensorOfferings;
    }

    public boolean addSensorOffering(SensorOfferingType sensorOfferingType) {
        return sensorOfferings.add(sensorOfferingType);
    }

    public boolean removeSensorOffering(String offeringIdentifier) {
        for (SensorOfferingType sensorOffering : sensorOfferings) {
            if (sensorOffering.getIdentifier().equals(offeringIdentifier)) {
                return sensorOfferings.remove(sensorOffering);
            }
        }
        return false;
    }

    public String getProcedure() {
        return procedure;
    }

    public void setProcedure(String procedure) {
        this.procedure = procedure;
    }

    public String getSensorPluginType() {
        return sensorPluginType;
    }

    public void setSensorPluginType(String sensorPluginType) {
        this.sensorPluginType = sensorPluginType;
    }

    public XmlObject getSensorSetup() {
        return sensorSetup;
    }
    
    public void setSensorSetup(XmlObject sensorSetup) {
        this.sensorSetup = sensorSetup;
    }
    
    public String getSensorSetupAsString() {
        return sensorSetup != null ? sensorSetup.xmlText(): null;
    }
    
    public void setSensorSetupAsString(String sensorSetup) throws XmlException {
        if (sensorSetup != null) {
            this.sensorSetup = XmlObject.Factory.parse(sensorSetup);
        }
    }
    
    public List<SensorDescription> getSensorDescriptions() {
        return sensorDescriptions;
    }

    public void setSensorDescriptions(List<SensorDescription> sensorDescriptions) {
        this.sensorDescriptions = sensorDescriptions;
    }

    public List<String> getSensorDescriptionUrisFor(String procedureDescriptionFormat) {
        List<String> descriptionUris = new ArrayList<String>();
        for (SensorDescription sensorDescription : sensorDescriptions) {
            if (sensorDescription.getProcedureDescriptionFormat().equals(procedureDescriptionFormat)) {
                descriptionUris.add(sensorDescription.getDownloadLink());
            }
        }
        return descriptionUris;
    }

    public boolean addSensorDescription(SensorDescription sensorDescription) {
        return sensorDescriptions.add(sensorDescription);
    }

    public boolean removeSensorDescription(SensorDescription sensorDescription) {
        return sensorDescriptions.remove(sensorDescription);
    }

    /**
     * @param procedureDescriptionFormat
     *        the format of SensorDescription
     * @return <code>true</code> if the given format is available, <code>false</code> otherwise.
     */
    public boolean supportsProcedureDescriptionFormat(String procedureDescriptionFormat) {
        for (SensorDescription sensorDescription : sensorDescriptions) {
            if (sensorDescription.getProcedureDescriptionFormat().equals(procedureDescriptionFormat)) {
                return true;
            }
        }
        return false;
    }

    public void setTaskingParametersTemplate(AbstractDataComponentType taskingParameters) {
        this.taskingParametersTemplate = taskingParameters;
    }

    /**
     * @return deep copy of configured tasking parameters to be used as template. The caller has to qualify
     *         the returned {@link AbstractDataComponentType} to a corresponding Substitution type.
     */
    public AbstractDataComponentType getTaskingParametersTemplate() {
        return (AbstractDataComponentType) taskingParametersTemplate.copy();
    }

    public String getTaskingParameters() {
        return taskingParametersTemplate.xmlText();
    }

    public void setTaskingParameters(String taskingParameters) throws XmlException {
        this.taskingParametersTemplate = AbstractDataComponentType.Factory.parse(taskingParameters);
    }

    public ResultAccessDataServiceReference getResultAccessDataService() {
        return resultAccessDataServiceTemplate;
    }

    public void setResultAccessDataService(ResultAccessDataServiceReference resultAccessDataService) {
        this.resultAccessDataServiceTemplate = resultAccessDataService;
    }

}
 public class SensorDescription {
    
    private Long id; // database id

    private String procedureDescriptionFormat;
    
    private String downloadLink;

    public SensorDescription(String procedureDescriptionFormat, String downloadLink) {
        this.procedureDescriptionFormat = procedureDescriptionFormat;
        this.downloadLink = downloadLink;
    }
    
    SensorDescription() {
        // db serialization
    }
    
    Long getId() {
        return id;
    }

    void setId(Long id) {
        this.id = id;
    }

    public String getProcedureDescriptionFormat() {
        return procedureDescriptionFormat;
    }

    public void setProcedureDescriptionFormat(String procedureDescriptionFormat) {
        this.procedureDescriptionFormat = procedureDescriptionFormat;
    }

    public String getDownloadLink() {
        return downloadLink;
    }

    public void setDownloadLink(String downloadLink) {
        this.downloadLink = downloadLink;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("SensorDescription [ ");
        sb.append("procedureDescriptionFormat: ").append(procedureDescriptionFormat);
        sb.append(" downloadLink: ").append(downloadLink).append(" ]");
        return sb.toString();
        
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ( (downloadLink == null) ? 0 : downloadLink.hashCode());
        result = prime * result + ( (procedureDescriptionFormat == null) ? 0 : procedureDescriptionFormat.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SensorDescription other = (SensorDescription) obj;
        if (downloadLink == null) {
            if (other.downloadLink != null)
                return false;
        }
        else if ( !downloadLink.equals(other.downloadLink))
            return false;
        if (procedureDescriptionFormat == null) {
            if (other.procedureDescriptionFormat != null)
                return false;
        }
        else if ( !procedureDescriptionFormat.equals(other.procedureDescriptionFormat))
            return false;
        return true;
    }
    
}
 public class SensorTask {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(SensorTask.class);
    
    private static final DateTimeFormatter ISO8601_FORMATTER = ISODateTimeFormat.dateTime();

    private Long id; // database id
    
    private String taskId;
    
    private String procedure;

    private Calendar estimatedToC;
    
    private SensorTaskStatus taskStatus;

    private double percentCompletion = 0d;

    private TaskingRequestStatus requestStatus = TaskingRequestStatus.PENDING;

    private Calendar updateTime = new GregorianCalendar(); // default;

    private List<String> statusMessages = new ArrayList<String>();

    private ParameterDataType parameterData;

    private String event;

    SensorTask() {
        // database serialization
    }
    
    public SensorTask(String taskId, String procedure) {
        checkNonEmptyParameter(taskId, procedure);
        this.taskId = taskId;
        this.procedure = procedure;
    }

    private void checkNonEmptyParameter(String taskId, String procedure) {
        if (taskId == null || taskId.isEmpty()) {
            LOGGER.error("Illegal parameter => taskId: {}", taskId);
            throw new IllegalArgumentException("taskId must not be null!");
        }
        if (procedure == null || procedure.isEmpty()) {
            LOGGER.error("Illegal parameter => procedure: {}", procedure);
            throw new IllegalArgumentException("procedure must not be null!");
        }
    }
    
    Long getId() {
        return id;
    }

    void setId(Long id) {
        // keep package private for database
        this.id = id;
    }

    public String getTaskId() {
        return taskId;
    }
    
    void setTaskId(String taskId) {
        // keep package private for database
        this.taskId = taskId;
    }
    
    public Calendar getCalendarInstance() {
        return updateTime;
    }

    public void setUpdateTime(Calendar calendar) {
        this.updateTime = calendar;
    }
    
    public Calendar getUpdateTime() {
        return updateTime;
    }

    public String getFormattedUpdateTime(DateFormat formatter) {
        return formatter.format(updateTime.getTime());
    }
    
    public String getUpdateTimeFormattedAsIso8601() {
        return ISO8601_FORMATTER.print(updateTime.getTimeInMillis());
    }

    /**
     * @param requestStatus
     *        according to REQ 14: <a
     *        href="http://www.opengis.net/spec/SPS/2.0/req/TaskingResponse/content">http
     *        ://www.opengis.net/spec/SPS/2.0/req/TaskingResponse/content</a>
     */
    public void setRequestStatus(TaskingRequestStatus requestStatus) {
        this.updateTime.setTimeInMillis(System.currentTimeMillis());
        this.requestStatus = requestStatus;
        if (!isAccepted()) {
            updateStatusTransitionEvent(RESET_TRANSITION_EVENT);
        } else {
            updateStatusTransitionEvent(TASK_SUBMITTED);
        } 
    }

    public void setRequestStatusAsString(String requestStatus) {
        if (requestStatus != null) {
            this.requestStatus = TaskingRequestStatus.getTaskingRequestStatus(requestStatus);
        }
    }

    private void updateStatusTransitionEvent(TaskStatusTransitionEvent transition) {
        event = transition.getEvent();
    }

    public TaskingRequestStatus getRequestStatus() {
        return requestStatus;
    }
    
    public String getRequestStatusAsString() {
        return requestStatus.toString();
    }

    /**
     * @return the estimated time when task will finish.
     */
    public Calendar getEstimatedToC() {
        return estimatedToC;
    }

    public void setEstimatedToC(Calendar estimatedToC) {
        this.estimatedToC = estimatedToC;
    }

    public double getPercentCompletion() {
        return percentCompletion;
    }

    public void setPercentCompletion(double percentCompletion) {
        this.percentCompletion = percentCompletion;
    }

    public String getProcedure() {
        return procedure;
    }
    
    void setProcedure(String procedure) {
        // keep package private for database
        this.procedure = procedure;
    }

    public void clearStatusMessages() {
        statusMessages.clear();
    }

    public void addStatusMessage(String message) {
        statusMessages.add(message);
    }
    
    public void setStatusMessages(List<String> statusMessages) {
        this.statusMessages = statusMessages;
    }

    public List<String> getStatusMessages() {
        return statusMessages;
    }

    /**
     * @param taskStatus
     *        according to REQ 15: <a
     *        href="http://www.opengis.net/spec/SPS/2.0/req/TaskingResponse/statusCodes"
     *        >http://www.opengis.net/spec/SPS/2.0/req/TaskingResponse/statusCodes</a>
     */
    public void setTaskStatus(SensorTaskStatus taskStatus) {
        this.taskStatus = taskStatus;
        if (isExecuting()) {
            updateStatusTransitionEvent(TASK_SUBMITTED);
        } else if (isFinished()) {
            updateStatusTransitionEvent(TASK_COMPLETED);
            setPercentCompletion(100.0);
        } else if (isExpired()) {
            updateStatusTransitionEvent(TASKING_REQUEST_EXPIRED);
        } else {
            updateStatusTransitionEvent(RESET_TRANSITION_EVENT);
        }
    }
    
    public void setTaskStatusAsString(String taskStatus) {
        if (taskStatus != null) {
            this.taskStatus = SensorTaskStatus.getSensorTaskStatus(taskStatus);
        }
    }

    public String getFormattedEstimatedToC(DateFormat formatter) {
        return formatter.format(estimatedToC.getTime());
    }
    
    public String getEstimatedToCFormattedAsIso8601() {
        return ISO8601_FORMATTER.print(estimatedToC.getTimeInMillis());
    }
    
    public SensorTaskStatus getTaskStatus()  {
        return taskStatus;
    }

    public String getTaskStatusAsString() {
        return taskStatus != null ? taskStatus.toString() : null;
    }
    
    public ParameterDataType getParameterData() {
        return parameterData;
    }

    public String getParameterDataAsString() {
        return parameterData != null ? parameterData.xmlText() : null;
    }

    public void setParameterData(ParameterDataType parameterData) {
        this.parameterData = parameterData;
    }
    
    public void setParameterDataAsString(String parameterData) throws XmlException {
        if (parameterData != null) {
            this.parameterData = ParameterDataType.Factory.parse(parameterData);
        }
    }
    
    public String getEvent() {
        return event;
    }
    
    void setEvent(String event) {
        this.event = event;
    }
    
    public TaskType getTask() throws StatusInformationExpiredException {
        StatusReportGenerator statusReporter = StatusReportGenerator.createFor(this);
        TaskType taskType = TaskType.Factory.newInstance();
        taskType.setIdentifier(getTaskId());
        StatusReportPropertyType status = taskType.addNewStatus();
        status.setStatusReport(statusReporter.generateWithoutTaskingParameters());
        return taskType;
    }

    /**
     * @deprecated use {@link StatusReportGenerator} instead.
     * @return
     * @throws StatusInformationExpiredException
     */
    @Deprecated
    public StatusReportType generateStatusReport() throws StatusInformationExpiredException {
        if (taskStatus == SensorTaskStatus.EXPIRED) {
            throw new StatusInformationExpiredException();
        }
        StatusReportType statusReport = StatusReportType.Factory.newInstance();
        statusReport.setTask(getTaskId());
        statusReport.setProcedure(getProcedure());
        statusReport.setRequestStatus(getRequestStatusAsString());
        statusReport.setUpdateTime(updateTime);
        if (estimatedToC != null) {
            statusReport.setEstimatedToC(estimatedToC);
        }

        // TODO implement event

        if (percentCompletion >= 0d) {
            statusReport.setPercentCompletion(percentCompletion);
        }
        if ( !statusMessages.isEmpty()) {
            for (String message : statusMessages) {
                LanguageStringType statusMessage = statusReport.addNewStatusMessage();
                statusMessage.setStringValue(message);
            }
        }
        if (taskStatus != null) {
            statusReport.setTaskStatus(getTaskStatusAsString());
        }

        // TODO implement alternative

        if (parameterData != null) {
            statusReport.addNewTaskingParameters().setParameterData(parameterData);
        }
        return statusReport;
    }
    
    public boolean isExpired() {
        return taskStatus == SensorTaskStatus.EXPIRED;
    }
    
    public boolean isFinished() {
        return taskStatus == SensorTaskStatus.COMPLETED;
    }
    
    public boolean isAccepted() {
        return requestStatus == TaskingRequestStatus.ACCEPTED;
    }

    public boolean isExecuting() {
        return taskStatus == SensorTaskStatus.INEXECUTION;
    }
    
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ( (procedure == null) ? 0 : procedure.hashCode());
        result = prime * result + ( (taskId == null) ? 0 : taskId.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SensorTask other = (SensorTask) obj;
        if (procedure == null) {
            if (other.procedure != null)
                return false;
        }
        else if ( !procedure.equals(other.procedure))
            return false;
        if (taskId == null) {
            if (other.taskId != null)
                return false;
        }
        else if ( !taskId.equals(other.taskId))
            return false;
        return true;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("TaskId: ").append(getTaskId()).append(", ");
        sb.append("Procedure: ").append(getProcedure()).append(", ");
        sb.append("RequestStatus: ").append(requestStatus).append(", ");
        sb.append("UpdateTime: ").append(getUpdateTimeFormattedAsIso8601());
        return sb.toString();
    }

}
 public class IllegalPluginTypeRelationException extends InternalServiceException{

    private static final long serialVersionUID = -3564776589779591554L;

    public IllegalPluginTypeRelationException(String locator) {
        super(InternalServiceExceptionCode.WRONG_PLUGIN_TYPE_RELATION.getExceptionCode(), locator);
    }

    @Override
    public int getHttpStatusCode() {
        return InternalServiceException.BAD_REQUEST;
    }

}
 public abstract class InternalServiceException extends Exception {
    
    private static final long serialVersionUID = -1598594769157092219L;
    
    public static final int BAD_REQUEST = 400;
    
    private List<String> detailedMessages = new ArrayList<String>();
    private String exceptionCode;
    private String locator;

    public InternalServiceException(String exceptionCode, String locator) {
        this.exceptionCode = exceptionCode;
        this.locator = locator;
    }

    public String getExceptionCode() {
        return exceptionCode;
    }

    public void setExceptionCode(String exceptionCode) {
        this.exceptionCode = exceptionCode;
    }

    public String getLocator() {
        return locator;
    }

    public void setLocator(String locator) {
        this.locator = locator;
    }

    public void addDetailedMessage(String detailedMessage) {
        detailedMessages.add(detailedMessage);
    }
    
    public Iterable<String> getDetailedMessages() {
        return detailedMessages;
    }

    public abstract int getHttpStatusCode();

    
}
 public class PluginTypeConfigurationException extends InternalServiceException {

    private static final long serialVersionUID = 6970265144159557472L;

    public PluginTypeConfigurationException(String locator) {
        super(InternalServiceExceptionCode.UNKNOWN_SERVICE_INSTANCE.getExceptionCode(), locator);
    }

    @Override
    public int getHttpStatusCode() {
        return InternalServiceException.BAD_REQUEST;

    }

}
 public class InsertSensorOfferingEvent {
    
    private InsertSensorOfferingDocument sensorOffering;

    public InsertSensorOfferingEvent(InsertSensorOfferingDocument sensorOffering) {
        this.sensorOffering = sensorOffering;
    }

    public InsertSensorOfferingDocument getInsertSensorOffering() {
        return sensorOffering;
    }


}
 public interface InsertSensorOfferingListener {

    public void handleInsertSensorOffering(InsertSensorOfferingEvent insertSensorOfferingEvent) throws InternalServiceException;
}
 public class InvalidSensorInformationException extends InternalServiceException {

    private static final long serialVersionUID = -7661454857813095434L;

    public InvalidSensorInformationException(String locator) {
        super(InternalServiceExceptionCode.MISSING_SENSOR_INFORMATION.toString(), locator);
    }

    @Override
    public int getHttpStatusCode() {
        return InternalServiceException.BAD_REQUEST;
    }

}
 public class MissingSensorInformationException extends InternalServiceException {

    private static final long serialVersionUID = -7661454857813095434L;

    public MissingSensorInformationException(String locator) {
        super(InternalServiceExceptionCode.MISSING_SENSOR_INFORMATION.toString(), locator);
    }

    @Override
    public int getHttpStatusCode() {
        return InternalServiceException.BAD_REQUEST;
    }

}
 public interface SensorConfigurationRepository {

    public void storeNewSensorConfiguration(SensorConfiguration sensorConfiguration);
    
    public void saveOrUpdateSensorConfiguration(SensorConfiguration sensorConfiguration);
    
    public void removeSensorConfiguration(SensorConfiguration sensorConfiguration);

    public SensorConfiguration getSensorConfiguration(String procedure);

    public Iterable<SensorConfiguration> getSensorConfigurations();

    public boolean containsSensorConfiguration(String procedure);

}
 public interface SensorTaskRepository {

    /**
     * Creates a new {@link SensorTask}. It is added to the {@link SensorTaskRepository} so it can be accessed
     * for further requests independent on if the task was {@link TaskingRequestStatus#ACCEPTED}, or
     * {@link TaskingRequestStatus#REJECTED} or stays in {@link TaskingRequestStatus#PENDING} state.
     * 
     * @param procedure
     *        the procedure the new task belongs to.
     * @return a new SensorTask with request status {@link TaskingRequestStatus#PENDING}
     */
    public SensorTask createNewSensorTask(String procedure);

    public void saveOrUpdateSensorTask(SensorTask sensorTask);

    public void removeSensorTask(SensorTask sensorTask);
    
    /**
     * Checks if a task with id <code>taskId</code> exists for the given procedure.
     * 
     * @param procedure the procedure to check
     * @param taskId the task id
     * @return <code>true</code> if a task with given task id is available for the given <code>procedure</code>.
     */
    public boolean containsSensorTask(String procedure, String taskId);

    /**
     * @param procedure the procedure all sensor tasks ids shall be retrieved from repository.
     * @return access to all sensor task ids found.
     */
    public Iterable<String> getSensorTaskIds(String procedure);

    /**
     * @param procedure the procedure all sensor tasks shall be retrieved from repository.
     * @return access to all sensor tasks found.
     */
    public Iterable<SensorTask> getSensorTasks(String procedure);

    /**
     * @param procedure
     *        the procedure the task belongs to.
     * @param taskId
     *        the task id to lookup SensorTasks for.
     * @return the sensor task associated with given <code>taskId</code>.
     * @throws InvalidParameterValueException
     *         if no SensorTask could be found (according to REQ 4: <a
     *         href="http://www.opengis.net/spec/SPS/2.0/req/exceptions/UnknownIdentifier"
     *         >http://www.opengis.net/spec/SPS/2.0/req/exceptions/UnknownIdentifier</a>)
     */
    public SensorTask getSensorTask(String procedure, String taskId) throws InvalidParameterValueException;
    
    /**
     * Retrieves the amount of stored tasks for a given procedure.
     * 
     * @param procedure the procedure the tasks shall belong to.
     * @return the amount of stored tasks in the repository
     */
    public long getTaskAmountOf(String procedure);

    public SensorTask getTask(String taskId);

}
 public class InsertSensorOfferingConverter {

    /**
     * Converts custom {@link InsertSensorOffering} type into well-known {@link SensorOfferingType}.
     * 
     * @param insertSensorOffering
     *        custom description document for inserting new sensor offerings.
     * @return a sensor offering as specified.
     */
    public SensorOfferingType convertToSensorOfferingType(final InsertSensorOffering insertSensorOffering) {
        SensorOfferingType sensorOfferingType = SensorOfferingType.Factory.newInstance();
        handleSensorOfferingContent(insertSensorOffering, sensorOfferingType);
        return sensorOfferingType;
    }

    private void handleSensorOfferingContent(InsertSensorOffering insertSensorOffering, SensorOfferingType sensorOfferingType) {
        addSensorOfferingData(insertSensorOffering.getSensorOfferingData(), sensorOfferingType);
        addSensorInstanceData(insertSensorOffering.getSensorInstanceData(), sensorOfferingType);
        addProcedureDescriptionFormats(insertSensorOffering.getSensorDescriptionDataArray(), sensorOfferingType);
    }

    private void addSensorOfferingData(SensorOfferingData sensorOfferingData, SensorOfferingType sensorOfferingType) {
        sensorOfferingType.setIdentifier(sensorOfferingData.getOfferingIdentifier());
        sensorOfferingType.setDescription(sensorOfferingData.getOfferingDescription());
        sensorOfferingType.setNameArray(sensorOfferingData.getOfferingNameArray());
        sensorOfferingType.setObservablePropertyArray(sensorOfferingData.getObservablePropertyArray());
        addRelatedFeatures(sensorOfferingData.getRelatedFeatureArray(), sensorOfferingType);
        addObservableArea(sensorOfferingData.getObservableArea(), sensorOfferingType);
    }

    private void addSensorInstanceData(SensorInstanceData sensorInstanceData, SensorOfferingType sensorOfferingType) {
        sensorOfferingType.setProcedure(sensorInstanceData.getProcedure().getStringValue());
    }

    private void addProcedureDescriptionFormats(SensorDescriptionData[] sensorDescriptionDataArray, SensorOfferingType sensorOfferingType) {
        String[] procedureDescriptionFormats = new String[sensorDescriptionDataArray.length];
        for (int i = 0; i < sensorDescriptionDataArray.length; i++) {
            procedureDescriptionFormats[i] = sensorDescriptionDataArray[i].getProcedureDescriptionFormat();
        }
        sensorOfferingType.setProcedureDescriptionFormatArray(procedureDescriptionFormats);
    }

    private void addRelatedFeatures(RelatedFeature[] relatedFeatureArray, SensorOfferingType sensorOfferingType) {
        for (RelatedFeature relatedFeature : relatedFeatureArray) {
            FeatureRelationshipType featureRelationship = relatedFeature.getFeatureRelationship();
            sensorOfferingType.addNewRelatedFeature().setFeatureRelationship(featureRelationship);
        }
    }

    private void addObservableArea(ObservableArea observableArea, SensorOfferingType sensorOffering) {
        if (observableArea.isSetByPoint()) {
            PointType point = observableArea.getByPoint().getPoint();
            point.setId(createRandomGmlId());
            sensorOffering.addNewObservableArea().addNewByPoint().setPoint(point);
        }
        else {
            PolygonType polygon = observableArea.getByPolygon().getPolygon();
            polygon.setId(createRandomGmlId());
            sensorOffering.addNewObservableArea().addNewByPolygon().setPolygon(polygon);
        }
    }
    
    String createRandomGmlId() {
        return "uuid_" + UUID.randomUUID().toString();
    }

}

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import net.opengis.ows.x11.LanguageStringType;
import net.opengis.ows.x11.MetadataType;
import net.opengis.ows.x11.ReferenceType;
import net.opengis.sps.x20.SPSMetadataDocument;
import net.opengis.sps.x20.SensorOfferingType;
import net.opengis.swe.x20.AbstractDataComponentType;
import net.opengis.swe.x20.DataChoiceType.Item;
import org.apache.xmlbeans.XmlException;
import org.n52.sps.sensor.SensorInstanceFactory;
import org.n52.sps.sensor.SensorPlugin;
import org.n52.sps.sensor.SensorTaskService;
import org.n52.sps.sensor.model.ResultAccessDataServiceReference;
import org.n52.sps.sensor.model.SensorConfiguration;
import org.n52.sps.sensor.model.SensorDescription;
import org.n52.sps.sensor.model.SensorTask;
import org.n52.sps.service.IllegalPluginTypeRelationException;
import org.n52.sps.service.InternalServiceException;
import org.n52.sps.service.PluginTypeConfigurationException;
import org.n52.sps.service.admin.InsertSensorOfferingEvent;
import org.n52.sps.service.admin.InsertSensorOfferingListener;
import org.n52.sps.service.admin.InvalidSensorInformationException;
import org.n52.sps.service.admin.MissingSensorInformationException;
import org.n52.sps.store.SensorConfigurationRepository;
import org.n52.sps.store.SensorTaskRepository;
import org.n52.sps.util.convert.InsertSensorOfferingConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.x52North.schemas.sps.v2.InsertSensorOfferingDocument;
import org.x52North.schemas.sps.v2.InsertSensorOfferingDocument.InsertSensorOffering;
import org.x52North.schemas.sps.v2.InsertSensorOfferingDocument.InsertSensorOffering.SensorDescriptionData;
import org.x52North.schemas.sps.v2.InsertSensorOfferingDocument.InsertSensorOffering.SensorTaskingParametersSet;
import org.x52North.schemas.sps.v2.InsertSensorOfferingDocument.InsertSensorOffering.SensorTaskingParametersSet.MultipleParameterSets;
import org.x52North.schemas.sps.v2.InsertSensorOfferingDocument.InsertSensorOffering.SensorTaskingParametersSet.SingleParameterSet;
import org.x52North.schemas.sps.v2.SensorInstanceDataDocument.SensorInstanceData;

/**
 * ﻿Copyright (C) 2012-${latestYearOfContribution} 52°North Initiative for Geospatial Open Source
 * Software GmbH
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 2 as publishedby the Free
 * Software Foundation.
 *
 * If the program is linked with libraries which are licensed under one of the
 * following licenses, the combination of the program with the linked library is
 * not considered a "derivative work" of the program:
 *
 *     - Apache License, version 2.0
 *     - Apache Software License, version 1.0
 *     - GNU Lesser General Public License, version 3
 *     - Mozilla Public License, versions 1.0, 1.1 and 2.0
 *     - Common Development and Distribution License (CDDL), version 1.0
 *
 * Therefore the distribution of the program linked with libraries licensed under
 * the aforementioned licenses, is permitted by the copyright holders if the
 * distribution is compliant with both the GNU General Public License version 2
 * and the aforementioned licenses.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 */
package org.n52.sps.service.core;




// TODO find a better name for this class. It initiates available sensor instances,
// but also provides access to tasks and sensor configurations via repository interface.
// Besides that is also is a handler for incoming InsertSensorOfferings
// => Think of how to separate all these concerns
public class SensorInstanceProvider implements InsertSensorOfferingListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SensorInstanceProvider.class);

    protected SensorConfigurationRepository sensorConfigurationRepository;

    protected SensorTaskRepository sensorTaskRepository;

    private ServiceLoader<SensorInstanceFactory> sensorPluginLoader;

    private List<SensorPlugin> sensorInstances = new ArrayList<SensorPlugin>();

    public void init() throws InternalServiceException {
        sensorPluginLoader = ServiceLoader.load(SensorInstanceFactory.class);
        if (isMissingRepository()) {
            throw new IllegalStateException("SensorInstanceProvider misses a repository.");
        }
        Iterable<SensorConfiguration> configs = sensorConfigurationRepository.getSensorConfigurations();
        for (SensorConfiguration sensorConfiguration : configs) {
            sensorInstances.add(initSensorInstance(sensorConfiguration));
        }
        LOGGER.info("Initialised {} with #{} sensor configuration(s).", getClass().getSimpleName(), sensorInstances.size());
    }

    private boolean isMissingRepository() {
        return sensorConfigurationRepository == null || sensorTaskRepository == null;
    }
    
    protected void addSensor(SensorPlugin sensorInstance) {
        // protected access to provide a seam for testing only
        sensorInstances.add(sensorInstance);
    }

    public Iterable<SensorPlugin> getSensors() {
        return sensorInstances;
    }
    
    public boolean containsTaskWith(String taskId) {
        return getTaskForTaskId(taskId) != null;
    }

    public SensorTask getTaskForTaskId(String taskId) {
        return getSensorTaskRepository().getTask(taskId);
    }
    
    public boolean containsSensorWith(String procedure) {
        return getSensorForProcedure(procedure) != null;
    }

    public SensorPlugin getSensorForProcedure(String procedure) {
        for (SensorPlugin sensorInstance : sensorInstances) {
            if (sensorInstance.getProcedure().equals(procedure)) {
                return sensorInstance;
            }
        }
        return null;
    }

    public SensorPlugin getSensorForTaskId(String taskId) {
        SensorTask sensorTask = sensorTaskRepository.getTask(taskId);
        return getSensorForProcedure(sensorTask.getProcedure());
    }

    public SensorConfigurationRepository getSensorConfigurationRepository() {
        return sensorConfigurationRepository;
    }

    public void setSensorConfigurationRepository(SensorConfigurationRepository sensorConfigurationRepository) throws InternalServiceException {
        this.sensorConfigurationRepository = sensorConfigurationRepository;
    }
    
    public SensorTaskRepository getSensorTaskRepository() {
        return sensorTaskRepository;
    }

    public void setSensorTaskRepository(SensorTaskRepository sensorTaskRepository) {
        this.sensorTaskRepository = sensorTaskRepository;
    }

    public void handleInsertSensorOffering(InsertSensorOfferingEvent insertSensorOfferingEvent) throws InternalServiceException {
        InsertSensorOfferingDocument insertSensorOfferingDocument = insertSensorOfferingEvent.getInsertSensorOffering();
        InsertSensorOffering insertSensorOffering = insertSensorOfferingDocument.getInsertSensorOffering();
        SensorOfferingType sensorOffering = createFromInsertSensorOffering(insertSensorOffering);
        SensorConfiguration sensorConfiguration = createSensorConfiguration(insertSensorOffering, sensorOffering);
        if (isSensorExisting(insertSensorOffering.getSensorInstanceData())) {
            SensorPlugin sensorPlugin = getSensorForProcedure(sensorOffering.getProcedure());
            sensorPlugin.mergeSensorConfigurations(sensorConfiguration);
            sensorConfigurationRepository.saveOrUpdateSensorConfiguration(sensorConfiguration);
        }
        else {
            checkMandatoryContentForNewSensorOffering(insertSensorOffering);
            sensorInstances.add(initSensorInstance(sensorConfiguration));
            sensorConfigurationRepository.storeNewSensorConfiguration(sensorConfiguration);
        }
    }

    private SensorPlugin initSensorInstance(SensorConfiguration sensorConfiguration) throws InternalServiceException {
        String pluginType = sensorConfiguration.getSensorPluginType();
        SensorInstanceFactory factory = getSensorFactory(pluginType);
        SensorTaskService sensorTaskService = new SensorTaskService(sensorTaskRepository);
        return factory.createSensorPlugin(sensorTaskService, sensorConfiguration);
    }

    private void checkMandatoryContentForNewSensorOffering(InsertSensorOffering insertSensorOffering) throws MissingSensorInformationException {
        // XXX validating InsertSensorOffering may make this check unneccessary
        SensorDescriptionData[] sensorDescriptions = insertSensorOffering.getSensorDescriptionDataArray();
        if (sensorDescriptions == null || sensorDescriptions.length == 0) {
            LOGGER.warn("No sensor description found.");
            throw new MissingSensorInformationException("sensorDescription");
        }
        if (insertSensorOffering.getSensorTaskingParametersSet() == null) {
            LOGGER.warn("No sensor tasking parameters found.");
            throw new MissingSensorInformationException("sensorTaskingParameters");
        }
    }

    protected SensorInstanceFactory getSensorFactory(String pluginType) throws PluginTypeConfigurationException {
        Iterator<SensorInstanceFactory> iterator = sensorPluginLoader.iterator();
        while (iterator.hasNext()) {
            SensorInstanceFactory factory = iterator.next();
            if (factory.getPluginType().equals(pluginType)) {
                return factory;
            }
        }
        LOGGER.error("Unknown sensor plugin type or no factory has been configured for '{}'.", pluginType);
        throw new PluginTypeConfigurationException(pluginType);
    }

    private SensorOfferingType createFromInsertSensorOffering(InsertSensorOffering insertSensorOffering) {

        InsertSensorOfferingConverter converter = new InsertSensorOfferingConverter();
