Examples of RMDBean


Examples of org.apache.sandesha2.storage.beans.RMDBean

  private void addOutOfOrderInvokerBeansToList(String sequenceID,
      StorageManager storageManager, List list)throws SandeshaException{
    if (log.isDebugEnabled())
      log.debug("Enter: InOrderInvoker::addOutOfOrderInvokerBeansToList " + sequenceID + ", " + list);
   
    RMDBean rmdBean = SandeshaUtil.getRMDBeanFromSequenceId(storageManager, sequenceID);
   
    if(rmdBean != null && rmdBean.getOutOfOrderRanges() != null){
      RangeString rangeString = rmdBean.getOutOfOrderRanges();
      //we now have the set of ranges that can be delivered out of order.
      //Look for any invokable message that lies in one of those ranges
      InvokerBean selector = new InvokerBean();
      selector.setSequenceID(sequenceID);
      Iterator invokerBeansIterator =
View Full Code Here

Examples of org.apache.sandesha2.storage.beans.RMDBean

      SequenceEntry entry = (SequenceEntry) allSequencesList.get(nextIndex++);
      String sequenceId = entry.getSequenceId();
      log.debug("Chose sequence " + sequenceId);

      RMDBean nextMsgBean = nextMsgMgr.retrieve(sequenceId);
      if (nextMsgBean == null) {
        log.debug("Next message not set correctly. Removing invalid entry.");

        stopThreadForSequence(sequenceId, entry.isRmSource());
        allSequencesList = getSequences();
        if (allSequencesList.size() == 0)
          sleep = true;

        if (log.isDebugEnabled()) log.debug("Exit: Invoker::internalRun, sleep " + sleep);
        return sleep;
      }

      long nextMsgno = nextMsgBean.getNextMsgNoToProcess();
      if (nextMsgno <= 0) {
        // Make sure we sleep on the next loop, so that we don't spin in a tight loop
        sleep = true;
        if (log.isDebugEnabled())
          log.debug("Invalid Next Message Number " + nextMsgno);
View Full Code Here

Examples of org.apache.sandesha2.storage.beans.RMDBean

    long msgNo = sequence.getMessageNumber().getMessageNumber();
    boolean lastMessage = sequence.getLastMessage() != null;
   
    // Check that both the Sequence header and message body have been secured properly
    RMDBeanMgr mgr = storageManager.getRMDBeanMgr();
    RMDBean bean = mgr.retrieve(sequenceId);
   
    if(bean != null && bean.getSecurityTokenData() != null) {
      SecurityManager secManager = SandeshaUtil.getSecurityManager(msgCtx.getConfigurationContext());
     
      QName seqName = new QName(rmMsgCtx.getRMNamespaceValue(), Sandesha2Constants.WSRM_COMMON.SEQUENCE);
     
      SOAPEnvelope envelope = msgCtx.getEnvelope();
      OMElement body = envelope.getBody();
      OMElement seqHeader = envelope.getHeader().getFirstChildWithName(seqName);
     
      SecurityToken token = secManager.recoverSecurityToken(bean.getSecurityTokenData());
     
      secManager.checkProofOfPossession(token, seqHeader, msgCtx);
      secManager.checkProofOfPossession(token, body, msgCtx);
    }
   
    // Store the inbound sequence id, number and lastMessage onto the operation context
    OperationContext opCtx = msgCtx.getOperationContext();
    if(opCtx != null) {
      opCtx.setProperty(Sandesha2Constants.MessageContextProperties.INBOUND_SEQUENCE_ID, sequenceId);
      opCtx.setProperty(Sandesha2Constants.MessageContextProperties.INBOUND_MESSAGE_NUMBER, new Long(msgNo));
      if(lastMessage) opCtx.setProperty(Sandesha2Constants.MessageContextProperties.INBOUND_LAST_MESSAGE, Boolean.TRUE);
    }
   
    // setting acked msg no range
    ConfigurationContext configCtx = rmMsgCtx.getMessageContext().getConfigurationContext();
    if (configCtx == null) {
      String message = SandeshaMessageHelper.getMessage(SandeshaMessageKeys.configContextNotSet);
      log.debug(message);
      throw new SandeshaException(message);
    }

    if (FaultManager.checkForUnknownSequence(rmMsgCtx, sequenceId, storageManager, false)) {
      if (log.isDebugEnabled())
        log.debug("Exit: SequenceProcessor::processReliableMessage, Unknown sequence");
      return InvocationResponse.ABORT;
    }

    // throwing a fault if the sequence is terminated
    if (FaultManager.checkForSequenceTerminated(rmMsgCtx, sequenceId, bean, false)) {
      if (log.isDebugEnabled())
        log.debug("Exit: SequenceProcessor::processReliableMessage, Sequence terminated");
      return InvocationResponse.ABORT;
    }
   
    // throwing a fault if the sequence is closed.
    if (FaultManager.checkForSequenceClosed(rmMsgCtx, sequenceId, bean, false)) {
      if (log.isDebugEnabled())
        log.debug("Exit: SequenceProcessor::processReliableMessage, Sequence closed");
      return InvocationResponse.ABORT;
    }
    FaultManager.checkForLastMsgNumberExceeded(rmMsgCtx, storageManager);
   
    if (FaultManager.checkForMessageRolledOver(rmMsgCtx, sequenceId, msgNo)) {
     
      if (log.isDebugEnabled())
        log.debug("Exit: SequenceProcessor::processReliableMessage, Message rolled over " + msgNo);
     
      return InvocationResponse.ABORT;
    }

    // Pause the messages bean if not the right message to invoke.
   
    // updating the last activated time of the sequence.
    bean.setLastActivatedTime(System.currentTimeMillis());
   
    if (lastMessage) {
      //setting this as the LastMessage number
      bean.setLastInMessageId(msgCtx.getMessageID());
    }
   
    EndpointReference replyTo = rmMsgCtx.getReplyTo();
    String key = SandeshaUtil.getUUID(); // key to store the message.
    // updating the Highest_In_Msg_No property which gives the highest
    // message number retrieved from this sequence.
    long highestInMsgNo = bean.getHighestInMessageNumber();

    if (msgNo > highestInMsgNo) {
      // If WS-Addressing is turned off there may not be a message id written into the SOAP
      // headers, but we can still fake one up to help us match up requests and replies within
      // this end of the connection.
      String messageId = msgCtx.getMessageID();
      if(messageId == null) {
        messageId = SandeshaUtil.getUUID();
        msgCtx.setMessageID(messageId);
      }
     
      bean.setHighestInMessageId(messageId);
      bean.setHighestInMessageNumber(msgNo);
    }
   
    String specVersion = rmMsgCtx.getRMSpecVersion();
    if (rmMsgCtx.getMessageContext().getAxisOperation().getName().getLocalPart().equals(Sandesha2Constants.RM_DUPLICATE_OPERATION.getLocalPart())
        && (Sandesha2Constants.QOS.InvocationType.DEFAULT_INVOCATION_TYPE == Sandesha2Constants.QOS.InvocationType.EXACTLY_ONCE)) {
      // this is a duplicate message and the invocation type is EXACTLY_ONCE. We try to return
      // ack messages at this point, as if someone is sending duplicates then they may have
      // missed earlier acks. We also have special processing for sync 2-way with RM 1.0
      if((replyTo==null || replyTo.hasAnonymousAddress()) &&
         (specVersion!=null && specVersion.equals(Sandesha2Constants.SPEC_VERSIONS.v1_0))) {

        SenderBeanMgr senderBeanMgr = storageManager.getSenderBeanMgr();
        SenderBean findSenderBean = new SenderBean ();
       
        if (rmMsgCtx.getMessageType()==Sandesha2Constants.MessageTypes.LAST_MESSAGE)
          findSenderBean.setMessageType(Sandesha2Constants.MessageTypes.LAST_MESSAGE);
        else
          findSenderBean.setMessageType(Sandesha2Constants.MessageTypes.APPLICATION);
       
        findSenderBean.setInboundSequenceId(sequence.getIdentifier().getIdentifier());
        findSenderBean.setInboundMessageNumber(sequence.getMessageNumber().getMessageNumber());
        findSenderBean.setSend(true);
   
        SenderBean replyMessageBean = senderBeanMgr.findUnique(findSenderBean);
         
        // this is effectively a poll for the replyMessage, so re-use the logic in the MakeConnection
        // processor. This will use this thread to re-send the reply, writing it into the transport.
        // As the reply is now written we do not want to continue processing, or suspend, so we abort.
        if(replyMessageBean != null) {
          if(log.isDebugEnabled()) log.debug("Found matching reply for replayed message");
           MakeConnectionProcessor.replyToPoll(rmMsgCtx, replyMessageBean, storageManager, false, null, transaction);
          result = InvocationResponse.ABORT;
          if (log.isDebugEnabled())
            log.debug("Exit: SequenceProcessor::processReliableMessage, replayed message: " + result);
          return result;
        }
      }
     
      EndpointReference acksTo = new EndpointReference (bean.getAcksToEPR());
     
      // Send an Ack if needed.
      //We are not sending acks for duplicate messages in the RM 1.0 anon InOut case.
      //If a standalone ack get sent before the actualy message (I.e. before the original msg get
      //replied), the client may take this as a InOnly message and may avoid looking for the application
      //response.
      if (!(Sandesha2Constants.SPEC_VERSIONS.v1_0.equals(rmMsgCtx.getRMSpecVersion()) &&
          rmMsgCtx.getReplyTo().hasAnonymousAddress())) {
        sendAckIfNeeded(bean, sequenceId, rmMsgCtx, storageManager, true, acksTo.hasAnonymousAddress())
      }
     
      result = InvocationResponse.ABORT;
      if (log.isDebugEnabled())
        log.debug("Exit: SequenceProcessor::processReliableMessage, dropping duplicate: " + result);
      return result;
    }
   
    // If the message is a reply to an outbound message then we can update the RMSBean that
    // matches.
    EndpointReference toEPR = msgCtx.getTo();
    if(toEPR == null || toEPR.hasAnonymousAddress()) {
      RMSBean outBean = null;

      // Look for the correct outbound sequence by checking the anon uuid (if there is one)
      String toAddress = (toEPR == null) ? null : toEPR.getAddress();
      if(SandeshaUtil.isWSRMAnonymous(toAddress)) {
        RMSBean finderBean = new RMSBean();
        finderBean.setAnonymousUUID(toAddress);
        outBean = storageManager.getRMSBeanMgr().findUnique(finderBean);
      }
     
      // Fall back to the sequence that may have been offered at sequence creation time
      if(outBean == null) {
        String outboundSequence = bean.getOutboundInternalSequence();
        if(outboundSequence != null) {
          outBean = SandeshaUtil.getRMSBeanFromInternalSequenceId(storageManager, outboundSequence);
        }
      }
     
      // Update the reply count
      if(outBean != null && outBean.getExpectedReplies() > 0 ) {
        outBean.setExpectedReplies(outBean.getExpectedReplies() - 1);
        RMSBeanMgr outMgr = storageManager.getRMSBeanMgr();
        outMgr.update(outBean);
      }
    }
   
    // Set the last activated time
    bean.setLastActivatedTime(System.currentTimeMillis());
   
    // Update the RMD bean
    mgr.update(bean);
   
    // If we are doing sync 2-way over WSRM 1.0, then we may just have received one of
    // the reply messages that we were looking for. If so we can remove the matching sender bean.
    int mep = msgCtx.getAxisOperation().getAxisSpecifMEPConstant();
    if(specVersion!=null && specVersion.equals(Sandesha2Constants.SPEC_VERSIONS.v1_0) &&
        mep == WSDLConstants.MEP_CONSTANT_OUT_IN) {
      RelatesTo relatesTo = msgCtx.getRelatesTo();
      if(relatesTo != null) {
        String messageId = relatesTo.getValue();
        SenderBean matcher = new SenderBean();
        matcher.setMessageID(messageId);
        SenderBean sender = storageManager.getSenderBeanMgr().findUnique(matcher);
        if(sender != null) {
          if(log.isDebugEnabled()) log.debug("Deleting sender for sync-2-way message");
         
          storageManager.removeMessageContext(sender.getMessageContextRefKey());
         
          //this causes the request to be deleted even without an ack.
          storageManager.getSenderBeanMgr().delete(messageId);
         
          // Try and terminate the corresponding outbound sequence
          RMSBean rmsBean = SandeshaUtil.getRMSBeanFromSequenceId(storageManager, sender.getSequenceID());
          TerminateManager.checkAndTerminate(rmMsgCtx.getConfigurationContext(), storageManager, rmsBean);
        }
      }
    }

    //setting properties for the messageContext
    rmMsgCtx.setProperty(Sandesha2Constants.MessageContextProperties.SEQUENCE_ID,sequenceId);
    rmMsgCtx.setProperty(Sandesha2Constants.MessageContextProperties.MESSAGE_NUMBER,new Long (msgNo));
   
    // We only create an ack message if:
    // - We have anonymous acks, and the backchannel is free
    // - We have async acks
    boolean backchannelFree = (replyTo != null && !replyTo.hasAnonymousAddress()) ||
                  WSDLConstants.MEP_CONSTANT_IN_ONLY == mep;
   
    boolean sendAck = false;
   
    boolean ackBackChannel = SpecSpecificConstants.sendAckInBackChannel (rmMsgCtx.getMessageType());
    EndpointReference acksTo = new EndpointReference (bean.getAcksToEPR());
    if (acksTo.hasAnonymousAddress() && backchannelFree && ackBackChannel) {
      Object responseWritten = msgCtx.getOperationContext().getProperty(Constants.RESPONSE_WRITTEN);
      if (responseWritten==null || !Constants.VALUE_TRUE.equals(responseWritten)) {       
        sendAck = true;
      }
View Full Code Here

Examples of org.apache.sandesha2.storage.beans.RMDBean

     
      // Update the bean in the map
      storageManager.getRMSBeanMgr().update(rmsBean);
    }
    else {
      RMDBean rmdBean = SandeshaUtil.getRMDBeanFromSequenceId(storageManager, sequenceID);
      if (rmdBean != null) {
        rmMsgCtx.pause();
       
        // Cleanup sending side.
        if (log.isDebugEnabled())
          log.debug("Terminating sending sequence " + rmdBean);
        TerminateManager.cleanReceivingSideOnTerminateMessage(configCtx, rmdBean.getSequenceID(), storageManager);
       
        // Update the last activated time.
        rmdBean.setLastActivatedTime(System.currentTimeMillis());
       
        // Update the bean in the map
        storageManager.getRMDBeanMgr().update(rmdBean);
     
      }
View Full Code Here

Examples of org.apache.sandesha2.storage.beans.RMDBean

        boolean highestMessage = false;
        if (sequence.getLastMessage() != null) {
          //this will work for RM 1.0 only
          highestMessage = true;
        } else {
          RMDBean rmdBean = SandeshaUtil.getRMDBeanFromSequenceId(storageManager, invokerBean.getSequenceID());
         
          if (rmdBean!=null && rmdBean.isTerminated()) {
            long highestInMsgNo = rmdBean.getHighestInMessageNumber();
            if (invokerBean.getMsgNo()==highestInMsgNo)
              highestMessage = true;
          }
        }
       
        if (highestMessage) {
          //do cleaning stuff that hs to be done after the invocation of the last message.
          TerminateManager.cleanReceivingSideAfterInvocation(invokerBean.getSequenceID(), storageManager);
          // exit from current iteration. (since an entry
          // was removed)
          if(log.isDebugEnabled()) log.debug("Exit: InvokerWorker::run Last message return");         
          return;
        }
      }
     
      if(!ignoreNextMsg){
        // updating the next msg to invoke
        RMDBean rMDBean = storageManager.getRMDBeanMgr().retrieve(invokerBean.getSequenceID());
        long nextMsgNo = rMDBean.getNextMsgNoToProcess();
       
        if (!(invokerBean.getMsgNo()==nextMsgNo)) {
          String message = "Operated message number is different from the Next Message Number to invoke";
          throw new SandeshaException (message);
        }
       
        nextMsgNo++;
        rMDBean.setNextMsgNoToProcess(nextMsgNo);
        storageManager.getRMDBeanMgr().update(rMDBean);
      }
     
      if(transaction != null && transaction.isActive()) transaction.commit();
      transaction = null;
View Full Code Here

Examples of org.apache.sandesha2.storage.beans.RMDBean

        RMSBean rmsBean = SandeshaUtil.getRMSBeanFromSequenceId(
            storageManager, sequenceID);
        if (rmsBean != null)
          rmVersionOfSequence = rmsBean.getRMVersion();
        else {
          RMDBean rmdBean = SandeshaUtil.getRMDBeanFromSequenceId(
              storageManager, sequenceID);
          if (rmdBean != null)
            rmVersionOfSequence = rmdBean.getRMVersion();
        }

        String rmNamespaceOfMsg = rmMsg.getRMNamespaceValue();
        String rmNamespaceOfSequence = null;
        if (rmVersionOfSequence != null)
View Full Code Here

Examples of org.apache.sandesha2.storage.beans.RMDBean

      // incoming sequences
      Collection rmdBeans = storageManager.getRMDBeanMgr().find(null);

      Iterator iter = rmdBeans.iterator();
      while (iter.hasNext()) {
        RMDBean serverCompletedMsgsBean = (RMDBean) iter.next();
        String sequenceID = serverCompletedMsgsBean.getSequenceID();
        sandeshaReport.addToIncomingSequenceList(sequenceID);

        SequenceReport sequenceReport = getIncomingSequenceReport(sequenceID, configurationContext);

        sandeshaReport.addToNoOfCompletedMessagesMap(sequenceID, sequenceReport.getCompletedMessages().size());
View Full Code Here

Examples of org.apache.sandesha2.storage.beans.RMDBean

  }

  private static byte getServerSequenceStatus(String sequenceID, StorageManager storageManager)
      throws SandeshaException {

    RMDBean rmdBean = SandeshaUtil.getRMDBeanFromSequenceId(storageManager, sequenceID);
    if (rmdBean != null && rmdBean.isTerminated()) {
      return SequenceReport.SEQUENCE_STATUS_TERMINATED;
    }

/*  Only outbound sequences time out 
    SequencePropertyBean timedOutBean = seqPropMgr.retrieve(sequenceID,
View Full Code Here

Examples of org.apache.sandesha2.storage.beans.RMDBean

    try {
      reportTransaction = storageManager.getTransaction();

      SequenceReport sequenceReport = new SequenceReport();

      RMDBean rmdBean = SandeshaUtil.getRMDBeanFromSequenceId(storageManager, sequenceID);

      List completedMessageList = rmdBean.getServerCompletedMessages().getContainedElementsAsNumbersList();
     
      Iterator iter = completedMessageList.iterator();
      while (iter.hasNext()) {
        sequenceReport.addCompletedMessage((Long) iter.next());
      }

      sequenceReport.setSequenceID(sequenceID);
      sequenceReport.setInternalSequenceID(sequenceID); // for the
                                // incoming side
                                // internalSequenceID=sequenceID
      sequenceReport.setSequenceDirection(SequenceReport.SEQUENCE_DIRECTION_IN);

      sequenceReport.setSequenceStatus(getServerSequenceStatus(sequenceID, storageManager));

      if(rmdBean.getSecurityTokenData() != null) sequenceReport.setSecureSequence(true);
     
      if (reportTransaction!=null && reportTransaction.isActive()) reportTransaction.commit();
      reportTransaction = null;

      return sequenceReport;
View Full Code Here

Examples of org.apache.sandesha2.storage.beans.RMDBean

      MessageContext outMessage = null;
 
      // Create the new sequence id, as well as establishing the beans that handle the
      // sequence state.
      RMDBean rmdBean = SequenceManager.setupNewSequence(createSeqRMMsg, storageManager, secManager, token);
       
      RMMsgContext createSeqResponse = RMMsgCreator.createCreateSeqResponseMsg(createSeqRMMsg, rmdBean);
      outMessage = createSeqResponse.getMessageContext();
      // Set a message ID for this Create Sequence Response message
      outMessage.setMessageID(SandeshaUtil.getUUID());
       
      createSeqResponse.setFlow(MessageContext.OUT_FLOW);
 
      // for making sure that this won't be processed again
      createSeqResponse.setProperty(Sandesha2Constants.APPLICATION_PROCESSING_DONE, "true");
     
      CreateSequenceResponse createSeqResPart = (CreateSequenceResponse) createSeqResponse
          .getMessagePart(Sandesha2Constants.MessageParts.CREATE_SEQ_RESPONSE);
 
        // OFFER PROCESSING
      SequenceOffer offer = createSeqPart.getSequenceOffer();
      if (offer != null) {
        Accept accept = createSeqResPart.getAccept();
        if (accept == null) {
          if (log.isDebugEnabled())
            log.debug(SandeshaMessageHelper.getMessage(SandeshaMessageKeys.noAcceptPart));
          FaultManager.makeCreateSequenceRefusedFault(createSeqRMMsg,
                                                      SandeshaMessageHelper.getMessage(SandeshaMessageKeys.noAcceptPart),
                                                      new Exception());
          // Return false if an Exception hasn't been thrown.
          if (log.isDebugEnabled())
            log.debug("Exit: CreateSeqMsgProcessor::processInMessage " + Boolean.FALSE);       
          return false;
        }
 
        // offered seq id
        String offeredSequenceID = offer.getIdentifer().getIdentifier();
       
        boolean offerEcepted = offerAccepted(offeredSequenceID, context, createSeqRMMsg, storageManager);
 
        if (offerEcepted) {
          // Setting the CreateSequence table entry for the outgoing
          // side.
          RMSBean rMSBean = new RMSBean();
          rMSBean.setSequenceID(offeredSequenceID);
          String outgoingSideInternalSequenceId = SandeshaUtil
              .getOutgoingSideInternalSequenceID(rmdBean.getSequenceID());
          rMSBean.setInternalSequenceID(outgoingSideInternalSequenceId);
          // this is a dummy value
          rMSBean.setCreateSeqMsgID(SandeshaUtil.getUUID());
           
          rMSBean.setToEPR(rmdBean.getToEPR());
          rMSBean.setAcksToEPR(rmdBean.getToEPR())// The acks need to flow back into this endpoint
          rMSBean.setReplyToEPR(rmdBean.getReplyToEPR());
          rMSBean.setLastActivatedTime(System.currentTimeMillis());
          rMSBean.setRMVersion(rmdBean.getRMVersion());
          rMSBean.setClientCompletedMessages(new RangeString());
 
          // Setting sequence properties for the outgoing sequence.
          // Only will be used by the server side response path. Will
          // be wasted properties for the client side.
           
          Endpoint endpoint = offer.getEndpoint();
          if (endpoint!=null) {
            // setting the OfferedEndpoint
            rMSBean.setOfferedEndPoint(endpoint.getEPR().getAddress());
          }
   
          rmdBean.setOutboundInternalSequence(outgoingSideInternalSequenceId);
          RMDBeanMgr rmdBeanMgr = storageManager.getRMDBeanMgr();
          rmdBeanMgr.update(rmdBean);
 
          // Store the inbound token (if any) with the new sequence
          rMSBean.setSecurityTokenData(rmdBean.getSecurityTokenData());
         
          // If this new sequence has anonymous acksTo, then we must poll for the acks
          // If the inbound sequence is targetted at the WSRM anonymous URI, we need to start
          // polling for this sequence.
          String acksTo = rMSBean.getAcksToEPR();
          EndpointReference reference = new EndpointReference(acksTo);
          if ((acksTo == null || reference.hasAnonymousAddress()) &&
            Sandesha2Constants.SPEC_VERSIONS.v1_1.equals(createSeqRMMsg.getRMSpecVersion())) {
            rMSBean.setPollingMode(true);
          }
         
          // Set the SOAP Version for this sequence.
          rMSBean.setSoapVersion(SandeshaUtil.getSOAPVersion(createSeqRMMsg.getSOAPEnvelope()));

          storageManager.getRMSBeanMgr().insert(rMSBean);
         
          SandeshaUtil.startWorkersForSequence(context, rMSBean);
         
        } else {
          // removing the accept part.
          createSeqResPart.setAccept(null);
          createSeqResponse.addSOAPEnvelope();
        }
      }
             
      //TODO add createSequenceResponse message as the referenceMessage to the RMDBean.
 
      outMessage.setResponseWritten(true);
 
      rmdBean.setLastActivatedTime(System.currentTimeMillis());
     
      // If the inbound sequence is targetted at the anonymous URI, we need to start
      // polling for this sequence.
      if (toEPR.hasAnonymousAddress()) {
        if (Sandesha2Constants.SPEC_VERSIONS.v1_1.equals(createSeqRMMsg.getRMSpecVersion())) {
          rmdBean.setPollingMode(true);
        }
      }
     
      storageManager.getRMDBeanMgr().update(rmdBean);
 
View Full Code Here
TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.